-
Notifications
You must be signed in to change notification settings - Fork 998
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(relay): correctly track associated state of inflight streams
In the relay client, we need to track a fair bit of data _alongside_ `Future`s that are executing within a `FuturesSet`. In particular, we need to track the channels that connect us the `Transport`. Using `?` within the actual `async` block of the protocol is crucial for ergonomics but essentially locks us out of passing the channel _into_ the `Future` itself. Hence, we need to track it outside. As we can see from #4822, doing so in a separate list is error prone. We solve this by introducing the `FuturesTupleSet` type to `futures-bounded`. This is a variation of `FuturesSet` that carries a piece of arbitrary data alongside the `Future` that is executing and returns it back upon completion. Using this within the relay code reveals another bug where we mistakenly confused a timed out `CONNECT` request for a timed out `RESERVE` request. Fixes: #4822. Pull-Request: #4841.
- Loading branch information
1 parent
e3db5a5
commit 41da782
Showing
8 changed files
with
147 additions
and
87 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
use std::collections::HashMap; | ||
use std::future::Future; | ||
use std::task::{ready, Context, Poll}; | ||
use std::time::Duration; | ||
|
||
use futures_util::future::BoxFuture; | ||
|
||
use crate::{FuturesMap, PushError, Timeout}; | ||
|
||
/// Represents a list of tuples of a [Future] and an associated piece of data. | ||
/// | ||
/// Each future must finish within the specified time and the list never outgrows its capacity. | ||
pub struct FuturesTupleSet<O, D> { | ||
id: u32, | ||
inner: FuturesMap<u32, O>, | ||
data: HashMap<u32, D>, | ||
} | ||
|
||
impl<O, D> FuturesTupleSet<O, D> { | ||
pub fn new(timeout: Duration, capacity: usize) -> Self { | ||
Self { | ||
id: 0, | ||
inner: FuturesMap::new(timeout, capacity), | ||
data: HashMap::new(), | ||
} | ||
} | ||
} | ||
|
||
impl<O, D> FuturesTupleSet<O, D> | ||
where | ||
O: 'static, | ||
{ | ||
/// Push a future into the list. | ||
/// | ||
/// This method adds the given future to the list. | ||
/// If the length of the list is equal to the capacity, this method returns a error that contains the passed future. | ||
/// In that case, the future is not added to the set. | ||
pub fn try_push<F>(&mut self, future: F, data: D) -> Result<(), (BoxFuture<O>, D)> | ||
where | ||
F: Future<Output = O> + Send + 'static, | ||
{ | ||
self.id = self.id.wrapping_add(1); | ||
|
||
match self.inner.try_push(self.id, future) { | ||
Ok(()) => {} | ||
Err(PushError::BeyondCapacity(w)) => return Err((w, data)), | ||
Err(PushError::Replaced(_)) => unreachable!("we never reuse IDs"), | ||
} | ||
self.data.insert(self.id, data); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn len(&self) -> usize { | ||
self.inner.len() | ||
} | ||
|
||
pub fn is_empty(&self) -> bool { | ||
self.inner.is_empty() | ||
} | ||
|
||
pub fn poll_ready_unpin(&mut self, cx: &mut Context<'_>) -> Poll<()> { | ||
self.inner.poll_ready_unpin(cx) | ||
} | ||
|
||
pub fn poll_unpin(&mut self, cx: &mut Context<'_>) -> Poll<(Result<O, Timeout>, D)> { | ||
let (id, res) = ready!(self.inner.poll_unpin(cx)); | ||
let data = self.data.remove(&id).expect("must have data for future"); | ||
|
||
Poll::Ready((res, data)) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use futures_util::future::poll_fn; | ||
use futures_util::FutureExt; | ||
use std::future::ready; | ||
|
||
#[test] | ||
fn tracks_associated_data_of_future() { | ||
let mut set = FuturesTupleSet::new(Duration::from_secs(10), 10); | ||
|
||
let _ = set.try_push(ready(1), 1); | ||
let _ = set.try_push(ready(2), 2); | ||
|
||
let (res1, data1) = poll_fn(|cx| set.poll_unpin(cx)).now_or_never().unwrap(); | ||
let (res2, data2) = poll_fn(|cx| set.poll_unpin(cx)).now_or_never().unwrap(); | ||
|
||
assert_eq!(res1.unwrap(), data1); | ||
assert_eq!(res2.unwrap(), data2); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters