fix(client): handle race condition when sending while connection is closing
This commit is contained in:
@@ -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> {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user