Fixed a race condition in get_ref
If two threads attempted to `get_ref` the same Header under two representations, then it was possible that the second thread would overwrite the work of the first thread, causing the first thread to do an unchecked downcast to the wrong type. In the case where they were the same Header representation, then the only damage would be that it would be parsed twice and would possibly return a false negative in one thread. The new code checks that it was not a queued lock and will not override the work of another thread, but will accept it if the other thread parsed the header as the same type. This also prevents duplicate parsing.
This commit is contained in:
@@ -160,38 +160,53 @@ impl Headers {
|
||||
/// Get a reference to the header field's value, if it exists.
|
||||
pub fn get_ref<H: Header>(&self) -> Option<&H> {
|
||||
self.data.find(&CaseInsensitive(Slice(header_name::<H>()))).and_then(|item| {
|
||||
let header = match *item.read() {
|
||||
let done = match *item.read() {
|
||||
// Huge borrowck hack here, should be refactored to just return here.
|
||||
Typed(ref typed) if typed.is::<H>() => None,
|
||||
// Typed, wrong type
|
||||
Typed(ref typed) if typed.is::<H>() => true,
|
||||
|
||||
// Typed, wrong type.
|
||||
Typed(_) => return None,
|
||||
Raw(ref raw) => match Header::parse_header(raw.as_slice()) {
|
||||
Some::<H>(h) => {
|
||||
Some(h)
|
||||
},
|
||||
None => return None
|
||||
},
|
||||
|
||||
// Raw, work to do.
|
||||
Raw(_) => false,
|
||||
};
|
||||
|
||||
match header {
|
||||
Some(header) => {
|
||||
*item.write() = Typed(box header as Box<Header + Send + Sync>);
|
||||
Some(item)
|
||||
},
|
||||
None => {
|
||||
Some(item)
|
||||
// borrowck hack continued
|
||||
if done { return Some(item); }
|
||||
|
||||
// Take out a write lock to do the parsing and mutation.
|
||||
let mut write = item.write();
|
||||
|
||||
let header = match *write {
|
||||
// Since this lock can queue, it's possible another thread just
|
||||
// did the work for us.
|
||||
//
|
||||
// Check they inserted the correct type and move on.
|
||||
Typed(ref typed) if typed.is::<H>() => return Some(item),
|
||||
|
||||
// Wrong type, another thread got here before us and parsed
|
||||
// as a different representation.
|
||||
Typed(_) => return None,
|
||||
|
||||
// We are first in the queue or the only ones, so do the actual
|
||||
// work of parsing and mutation.
|
||||
Raw(ref raw) => match Header::parse_header(raw.as_slice()) {
|
||||
Some::<H>(h) => h,
|
||||
None => return None
|
||||
}
|
||||
}
|
||||
}).and_then(|item| {
|
||||
};
|
||||
|
||||
// Mutate in the raw case.
|
||||
*write = Typed(box header as Box<Header + Send + Sync>);
|
||||
Some(item)
|
||||
}).map(|item| {
|
||||
let read = item.read();
|
||||
debug!("downcasting {}", *read);
|
||||
let ret = match *read {
|
||||
Typed(ref val) => {
|
||||
unsafe { Some(val.downcast_ref_unchecked()) }
|
||||
},
|
||||
Typed(ref val) => unsafe { val.downcast_ref_unchecked() },
|
||||
_ => unreachable!()
|
||||
};
|
||||
unsafe { transmute::<Option<&H>, Option<&H>>(ret) }
|
||||
unsafe { transmute::<&H, &H>(ret) }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user