fix(client): handle race condition when sending while connection is closing

This commit is contained in:
Sean McArthur
2018-04-24 13:58:07 -07:00
parent d127201ef2
commit 6906ced872
2 changed files with 70 additions and 69 deletions

View File

@@ -156,38 +156,14 @@ impl<T, U> Stream for Receiver<T, U> {
}
}
/*
TODO: with futures 0.2, bring this Drop back and toss Envelope
The problem is, there is a bug in futures 0.1 mpsc channel, where
even though you may call `rx.close()`, `rx.poll()` may still think
there are messages and so should park the current task. In futures
0.2, we can use `try_next`, and not even risk such a bug.
For now, use an `Envelope` that has this drop guard logic instead.
impl<T, U> Drop for Receiver<T, U> {
fn drop(&mut self) {
// Notify the giver about the closure first, before dropping
// the mpsc::Receiver.
self.taker.cancel();
self.inner.close();
// This poll() is safe to call in `Drop`, because we've
// called, `close`, which promises that no new messages
// will arrive, and thus, once we reach the end, we won't
// see a `NotReady` (and try to park), but a Ready(None).
//
// All other variants:
// - Ready(None): the end. we want to stop looping
// - NotReady: unreachable
// - Err: unreachable
while let Ok(Async::Ready(Some((val, cb)))) = self.inner.poll() {
let _ = cb.send(Err((::Error::new_canceled(None::<::Error>), Some(val))));
}
}
}
*/
struct Envelope<T, U>(Option<(T, Callback<T, U>)>);
impl<T, U> Drop for Envelope<T, U> {

View File

@@ -250,7 +250,32 @@ where C: Connect + Sync + 'static,
if ver == Ver::Http1 {
set_relative_uri(req.uri_mut(), pooled.is_proxied);
}
let fut = pooled.send_request_retryable(req)
let fut = pooled.send_request_retryable(req);
// As of futures@0.1.21, there is a race condition in the mpsc
// channel, such that sending when the receiver is closing can
// result in the message being stuck inside the queue. It won't
// ever notify until the Sender side is dropped.
//
// To counteract this, we must check if our senders 'want' channel
// has been closed after having tried to send. If so, error out...
if pooled.is_closed() {
drop(pooled);
let fut = fut
.map_err(move |(err, orig_req)| {
if let Some(req) = orig_req {
ClientError::Canceled {
connection_reused: conn_reused,
reason: err,
req,
}
} else {
ClientError::Normal(err)
}
});
Either::A(fut)
} else {
let fut = fut
.map_err(move |(err, orig_req)| {
if let Some(req) = orig_req {
ClientError::Canceled {
@@ -292,8 +317,8 @@ where C: Connect + Sync + 'static,
}
Ok(res)
});
fut
Either::B(fut)
}
});
Box::new(resp)