connection_reuse: drop dead connections. (#178)

Currently, connection substreams are added to
`connection_reuse::Shared::active_connections`, but never removed. This
is not least because the `StreamMuxer` trait defines its inbound and
outbound substream futures to always yield a substream and contains no
provision to signal that no more substreams can be created, which would
allow client code (e.g. `ConnectionReuse`) to detect this and purge its
caches.

This PR defines the `StreamMuxer` trait to optionally yield
inbound/outbound substreams and changes `libp2p-mplex` to handle
stream EOFs by marking the underlying resource as closed.
`ConnectionReuse` will remove stream muxers from its active connections
cache if a `None` substream is returned.
This commit is contained in:
Toralf Wittner
2018-05-14 14:49:29 +02:00
committed by Pierre Krieger
parent 11f655dd6a
commit 4382adcbde
8 changed files with 204 additions and 96 deletions

View File

@ -172,7 +172,7 @@ pub struct InboundFuture<T, Buf: Array> {
} }
impl<T: AsyncRead, Buf: Array<Item = u8>> Future for InboundFuture<T, Buf> { impl<T: AsyncRead, Buf: Array<Item = u8>> Future for InboundFuture<T, Buf> {
type Item = Substream<T, Buf>; type Item = Option<Substream<T, Buf>>;
type Error = io::Error; type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@ -181,6 +181,10 @@ impl<T: AsyncRead, Buf: Array<Item = u8>> Future for InboundFuture<T, Buf> {
Async::NotReady => return Ok(Async::NotReady), Async::NotReady => return Ok(Async::NotReady),
}; };
if lock.is_closed() {
return Ok(Async::Ready(None));
}
// Attempt to make progress, but don't block if we can't // Attempt to make progress, but don't block if we can't
match read_stream(&mut lock, None) { match read_stream(&mut lock, None) {
Ok(_) => {} Ok(_) => {}
@ -200,12 +204,12 @@ impl<T: AsyncRead, Buf: Array<Item = u8>> Future for InboundFuture<T, Buf> {
lock.open_stream(id); lock.open_stream(id);
Ok(Async::Ready(Substream::new( Ok(Async::Ready(Some(Substream::new(
id, id,
self.end, self.end,
name, name,
Arc::clone(&self.state), Arc::clone(&self.state),
))) ))))
} }
} }
@ -230,7 +234,7 @@ fn nonce_to_id(id: usize, end: Endpoint) -> u32 {
} }
impl<T: AsyncWrite, Buf: Array> Future for OutboundFuture<T, Buf> { impl<T: AsyncWrite, Buf: Array> Future for OutboundFuture<T, Buf> {
type Item = Substream<T, Buf>; type Item = Option<Substream<T, Buf>>;
type Error = io::Error; type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@ -239,6 +243,10 @@ impl<T: AsyncWrite, Buf: Array> Future for OutboundFuture<T, Buf> {
Async::NotReady => return Ok(Async::NotReady), Async::NotReady => return Ok(Async::NotReady),
}; };
if lock.is_closed() {
return Ok(Async::Ready(None));
}
loop { loop {
let (mut id_str, id) = self.current_id.take().unwrap_or_else(|| { let (mut id_str, id) = self.current_id.take().unwrap_or_else(|| {
let next = nonce_to_id( let next = nonce_to_id(
@ -260,12 +268,12 @@ impl<T: AsyncWrite, Buf: Array> Future for OutboundFuture<T, Buf> {
debug_assert!(id_str.position() <= id_str.get_ref().len() as u64); debug_assert!(id_str.position() <= id_str.get_ref().len() as u64);
if id_str.position() == id_str.get_ref().len() as u64 { if id_str.position() == id_str.get_ref().len() as u64 {
if lock.open_stream(id) { if lock.open_stream(id) {
return Ok(Async::Ready(Substream::new( return Ok(Async::Ready(Some(Substream::new(
id, id,
self.meta.end, self.meta.end,
Bytes::from(&id_str.get_ref()[..]), Bytes::from(&id_str.get_ref()[..]),
Arc::clone(&self.state), Arc::clone(&self.state),
))); ))));
} }
} else { } else {
self.current_id = Some((id_str, id)); self.current_id = Some((id_str, id));
@ -393,7 +401,12 @@ mod tests {
let mplex = Multiplex::dial(stream); let mplex = Multiplex::dial(stream);
let mut substream = mplex.clone().outbound().wait().unwrap(); let mut substream = mplex
.clone()
.outbound()
.wait()
.unwrap()
.expect("outbound substream");
assert!(tokio::write_all(&mut substream, message).wait().is_ok()); assert!(tokio::write_all(&mut substream, message).wait().is_ok());
@ -410,7 +423,7 @@ mod tests {
let mplex = Multiplex::listen(stream); let mplex = Multiplex::listen(stream);
let mut substream = mplex.inbound().wait().unwrap(); let mut substream = mplex.inbound().wait().unwrap().expect("inbound substream");
assert_eq!(id, substream.id()); assert_eq!(id, substream.id());
assert_eq!( assert_eq!(
@ -435,7 +448,14 @@ mod tests {
let mut outbound: Vec<Substream<_>> = vec![]; let mut outbound: Vec<Substream<_>> = vec![];
for _ in 0..5 { for _ in 0..5 {
outbound.push(mplex.clone().outbound().wait().unwrap()); outbound.push(
mplex
.clone()
.outbound()
.wait()
.unwrap()
.expect("outbound substream"),
);
} }
outbound.sort_by_key(|a| a.id()); outbound.sort_by_key(|a| a.id());
@ -455,7 +475,14 @@ mod tests {
let mut inbound: Vec<Substream<_>> = vec![]; let mut inbound: Vec<Substream<_>> = vec![];
for _ in 0..5 { for _ in 0..5 {
inbound.push(mplex.clone().inbound().wait().unwrap()); inbound.push(
mplex
.clone()
.inbound()
.wait()
.unwrap()
.expect("inbound substream"),
);
} }
inbound.sort_by_key(|a| a.id()); inbound.sort_by_key(|a| a.id());
@ -515,7 +542,7 @@ mod tests {
let mplex = Multiplex::listen(io::Cursor::new(input)); let mplex = Multiplex::listen(io::Cursor::new(input));
let mut substream = mplex.inbound().wait().unwrap(); let mut substream = mplex.inbound().wait().unwrap().expect("inbound substream");
assert_eq!(substream.id(), 0); assert_eq!(substream.id(), 0);
assert_eq!(substream.name(), None); assert_eq!(substream.name(), None);
@ -567,7 +594,7 @@ mod tests {
let mplex = Multiplex::listen(io::Cursor::new(input)); let mplex = Multiplex::listen(io::Cursor::new(input));
let mut substream = mplex.inbound().wait().unwrap(); let mut substream = mplex.inbound().wait().unwrap().expect("inbound substream");
assert_eq!(substream.id(), 0); assert_eq!(substream.id(), 0);
assert_eq!(substream.name(), None); assert_eq!(substream.name(), None);
@ -615,7 +642,7 @@ mod tests {
let mplex = Multiplex::listen(io::Cursor::new(data)); let mplex = Multiplex::listen(io::Cursor::new(data));
let mut substream = mplex.inbound().wait().unwrap(); let mut substream = mplex.inbound().wait().unwrap().expect("inbound substream");
assert_eq!(substream.id(), 1); assert_eq!(substream.id(), 1);
@ -650,7 +677,14 @@ mod tests {
let mut outbound: Vec<Substream<_, Buffer>> = vec![]; let mut outbound: Vec<Substream<_, Buffer>> = vec![];
for _ in 0..5 { for _ in 0..5 {
outbound.push(mplex.clone().outbound().wait().unwrap()); outbound.push(
mplex
.clone()
.outbound()
.wait()
.unwrap()
.expect("outbound substream"),
);
} }
outbound.sort_by_key(|a| a.id()); outbound.sort_by_key(|a| a.id());
@ -670,7 +704,12 @@ mod tests {
let mut inbound: Vec<Substream<_, Buffer>> = vec![]; let mut inbound: Vec<Substream<_, Buffer>> = vec![];
for _ in 0..5 { for _ in 0..5 {
let inb: Substream<_, Buffer> = mplex.clone().inbound().wait().unwrap(); let inb: Substream<_, Buffer> = mplex
.clone()
.inbound()
.wait()
.unwrap()
.expect("inbound substream");
inbound.push(inb); inbound.push(inb);
} }

View File

@ -235,6 +235,7 @@ fn read_stream_internal<T: AsyncRead, Buf: Array<Item = u8>>(
let header = if let Some(header) = header { let header = if let Some(header) = header {
header header
} else { } else {
lock.close();
return Ok(on_block.unwrap_or(0)); return Ok(on_block.unwrap_or(0));
}; };
@ -310,6 +311,7 @@ fn read_stream_internal<T: AsyncRead, Buf: Array<Item = u8>>(
let length = if let Some(length) = length { let length = if let Some(length) = length {
length length
} else { } else {
lock.close();
return Ok(on_block.unwrap_or(0)); return Ok(on_block.unwrap_or(0));
}; };
@ -377,6 +379,10 @@ fn read_stream_internal<T: AsyncRead, Buf: Array<Item = u8>>(
match consumed { match consumed {
Ok(consumed) => { Ok(consumed) => {
if consumed == 0 {
lock.close()
}
let new_remaining = remaining_bytes - consumed; let new_remaining = remaining_bytes - consumed;
lock.read_state = Some(NewStream { lock.read_state = Some(NewStream {
@ -446,6 +452,10 @@ fn read_stream_internal<T: AsyncRead, Buf: Array<Item = u8>>(
match read_result { match read_result {
Ok(consumed) => { Ok(consumed) => {
if consumed == 0 {
lock.close()
}
let new_remaining = remaining_bytes - consumed; let new_remaining = remaining_bytes - consumed;
lock.read_state = Some(ParsingMessageBody { lock.read_state = Some(ParsingMessageBody {
@ -493,6 +503,9 @@ fn read_stream_internal<T: AsyncRead, Buf: Array<Item = u8>>(
let new_len = ignore_buf.len().min(remaining_bytes); let new_len = ignore_buf.len().min(remaining_bytes);
match lock.stream.read(&mut ignore_buf[..new_len]) { match lock.stream.read(&mut ignore_buf[..new_len]) {
Ok(consumed) => { Ok(consumed) => {
if consumed == 0 {
lock.close()
}
remaining_bytes -= consumed; remaining_bytes -= consumed;
lock.read_state = Some(Ignore { lock.read_state = Some(Ignore {
substream_id, substream_id,

View File

@ -75,7 +75,7 @@ pub struct MultiplexShared<T, Buf: Array> {
pub read_state: Option<MultiplexReadState>, pub read_state: Option<MultiplexReadState>,
pub write_state: Option<MultiplexWriteState>, pub write_state: Option<MultiplexWriteState>,
pub stream: T, pub stream: T,
// true if the stream is open, false otherwise eof: bool, // true, if `stream` has been exhausted
pub open_streams: HashMap<u32, SubstreamMetadata<Buf>>, pub open_streams: HashMap<u32, SubstreamMetadata<Buf>>,
pub meta_write_tasks: Vec<Task>, pub meta_write_tasks: Vec<Task>,
// TODO: Should we use a version of this with a fixed size that doesn't allocate and return // TODO: Should we use a version of this with a fixed size that doesn't allocate and return
@ -93,6 +93,7 @@ impl<T, Buf: Array> MultiplexShared<T, Buf> {
meta_write_tasks: Default::default(), meta_write_tasks: Default::default(),
to_open: Default::default(), to_open: Default::default(),
stream: stream, stream: stream,
eof: false,
} }
} }
@ -108,6 +109,14 @@ impl<T, Buf: Array> MultiplexShared<T, Buf> {
trace!(target: "libp2p-mplex", "close stream {}", id); trace!(target: "libp2p-mplex", "close stream {}", id);
self.open_streams.insert(id, SubstreamMetadata::Closed); self.open_streams.insert(id, SubstreamMetadata::Closed);
} }
pub fn close(&mut self) {
self.eof = true
}
pub fn is_closed(&self) -> bool {
self.eof
}
} }
pub fn buf_from_slice(slice: &[u8]) -> ByteBuf { pub fn buf_from_slice(slice: &[u8]) -> ByteBuf {

View File

@ -56,7 +56,7 @@ fn client_to_server_outbound() {
.map_err(|(err, _)| err) .map_err(|(err, _)| err)
.and_then(|(client, _)| client.unwrap().map(|v| v.0)) .and_then(|(client, _)| client.unwrap().map(|v| v.0))
.and_then(|client| client.outbound()) .and_then(|client| client.outbound())
.map(|client| Framed::<_, bytes::BytesMut>::new(client)) .map(|client| Framed::<_, bytes::BytesMut>::new(client.unwrap()))
.and_then(|client| { .and_then(|client| {
client client
.into_future() .into_future()
@ -79,7 +79,7 @@ fn client_to_server_outbound() {
.dial(rx.recv().unwrap()) .dial(rx.recv().unwrap())
.unwrap() .unwrap()
.and_then(|client| client.0.inbound()) .and_then(|client| client.0.inbound())
.map(|server| Framed::<_, bytes::BytesMut>::new(server)) .map(|server| Framed::<_, bytes::BytesMut>::new(server.unwrap()))
.and_then(|server| server.send("hello world".into())) .and_then(|server| server.send("hello world".into()))
.map(|_| ()); .map(|_| ());
@ -108,7 +108,7 @@ fn client_to_server_inbound() {
.map_err(|(err, _)| err) .map_err(|(err, _)| err)
.and_then(|(client, _)| client.unwrap().map(|v| v.0)) .and_then(|(client, _)| client.unwrap().map(|v| v.0))
.and_then(|client| client.inbound()) .and_then(|client| client.inbound())
.map(|client| Framed::<_, bytes::BytesMut>::new(client)) .map(|client| Framed::<_, bytes::BytesMut>::new(client.unwrap()))
.and_then(|client| { .and_then(|client| {
client client
.into_future() .into_future()
@ -131,7 +131,7 @@ fn client_to_server_inbound() {
.dial(rx.recv().unwrap()) .dial(rx.recv().unwrap())
.unwrap() .unwrap()
.and_then(|(client, _)| client.outbound()) .and_then(|(client, _)| client.outbound())
.map(|server| Framed::<_, bytes::BytesMut>::new(server)) .map(|server| Framed::<_, bytes::BytesMut>::new(server.unwrap()))
.and_then(|server| server.send("hello world".into())) .and_then(|server| server.send("hello world".into()))
.map(|_| ()); .map(|_| ());

View File

@ -40,7 +40,7 @@
//! `MuxedTransport` trait. //! `MuxedTransport` trait.
use fnv::FnvHashMap; use fnv::FnvHashMap;
use futures::future::{self, FutureResult, IntoFuture}; use futures::future::{self, Either, FutureResult, IntoFuture};
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::stream::Fuse as StreamFuse; use futures::stream::Fuse as StreamFuse;
@ -48,7 +48,7 @@ use futures::sync::mpsc;
use multiaddr::Multiaddr; use multiaddr::Multiaddr;
use muxing::StreamMuxer; use muxing::StreamMuxer;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::io::Error as IoError; use std::io::{self, Error as IoError};
use std::sync::Arc; use std::sync::Arc;
use transport::{MuxedTransport, Transport, UpgradedNode}; use transport::{MuxedTransport, Transport, UpgradedNode};
use upgrade::ConnectionUpgrade; use upgrade::ConnectionUpgrade;
@ -152,52 +152,59 @@ where
fn dial(self, addr: Multiaddr) -> Result<Self::Dial, (Self, Multiaddr)> { fn dial(self, addr: Multiaddr) -> Result<Self::Dial, (Self, Multiaddr)> {
// If we already have an active connection, use it! // If we already have an active connection, use it!
if let Some(connec) = self.shared let substream = if let Some(muxer) = self.shared
.lock() .lock()
.active_connections .active_connections
.get(&addr) .get(&addr)
.map(|c| c.clone()) .map(|muxer| muxer.clone())
{ {
debug!(target: "libp2p-swarm", "Using existing multiplexed connection to {}", addr); let a = addr.clone();
let future = connec.outbound().map(|s| (s, addr)); Either::A(muxer.outbound().map(move |s| s.map(move |s| (s, a))))
return Ok(Box::new(future) as Box<_>); } else {
} Either::B(future::ok(None))
debug!(target: "libp2p-swarm", "No existing connection to {} ; dialing", addr);
// TODO: handle if we're already in the middle in dialing that same node?
// TODO: try dialing again if the existing connection has dropped
let dial = match self.inner.dial(addr) {
Ok(l) => l,
Err((inner, addr)) => {
warn!(target: "libp2p-swarm", "Failed to dial {} because the underlying \
transport doesn't support this address", addr);
return Err((
ConnectionReuse {
inner: inner,
shared: self.shared,
},
addr,
));
}
}; };
let shared = self.shared.clone(); let shared = self.shared.clone();
let dial = dial.into_future().and_then(move |(connec, addr)| { let inner = self.inner;
// Always replace the active connection because we are the most recent. let future = substream.and_then(move |outbound| {
let mut lock = shared.lock(); if let Some(o) = outbound {
lock.active_connections.insert(addr.clone(), connec.clone()); debug!(target: "libp2p-swarm", "Using existing multiplexed connection to {}", addr);
// TODO: doesn't need locking ; the sender could be extracted return Either::A(future::ok(o));
let _ = lock.add_to_next_tx.unbounded_send(( }
connec.clone(), // The previous stream muxer did not yield a new substream => start new dial
connec.clone().inbound(), debug!(target: "libp2p-swarm", "No existing connection to {}; dialing", addr);
addr.clone(), match inner.dial(addr.clone()) {
)); Ok(dial) => {
connec.outbound().map(|s| (s, addr)) let future = dial.into_future().and_then(move |(muxer, addr)| {
muxer.clone().outbound().and_then(move |substream| {
if let Some(s) = substream {
// Replace the active connection because we are the most recent.
let mut lock = shared.lock();
lock.active_connections.insert(addr.clone(), muxer.clone());
// TODO: doesn't need locking ; the sender could be extracted
let _ = lock.add_to_next_tx.unbounded_send((
muxer.clone(),
muxer.inbound(),
addr.clone(),
));
Ok((s, addr))
} else {
error!(target: "libp2p-swarm", "failed to dial to {}", addr);
shared.lock().active_connections.remove(&addr);
Err(io::Error::new(io::ErrorKind::Other, "dial failed"))
}
})
});
Either::B(Either::A(future))
}
Err(_) => {
let e = io::Error::new(io::ErrorKind::Other, "transport rejected dial");
Either::B(Either::B(future::err(e)))
}
}
}); });
Ok(Box::new(dial) as Box<_>) Ok(Box::new(future) as Box<_>)
} }
#[inline] #[inline]
@ -281,12 +288,6 @@ where
let next_incoming = muxer.clone().inbound(); let next_incoming = muxer.clone().inbound();
self.connections self.connections
.push((muxer.clone(), next_incoming, client_addr.clone())); .push((muxer.clone(), next_incoming, client_addr.clone()));
// We overwrite any current active connection to that multiaddr because we
// are the freshest possible connection.
self.shared
.lock()
.active_connections
.insert(client_addr, muxer);
} }
Err(err) => { Err(err) => {
// Insert the rest of the pending upgrades, but not the current one. // Insert the rest of the pending upgrades, but not the current one.
@ -301,7 +302,18 @@ where
for n in (0..self.connections.len()).rev() { for n in (0..self.connections.len()).rev() {
let (muxer, mut next_incoming, client_addr) = self.connections.swap_remove(n); let (muxer, mut next_incoming, client_addr) = self.connections.swap_remove(n);
match next_incoming.poll() { match next_incoming.poll() {
Ok(Async::Ready(incoming)) => { Ok(Async::Ready(None)) => {
// stream muxer gave us a `None` => connection should be considered closed
debug!(target: "libp2p-swarm", "no more inbound substreams on {}", client_addr);
self.shared.lock().active_connections.remove(&client_addr);
}
Ok(Async::Ready(Some(incoming))) => {
// We overwrite any current active connection to that multiaddr because we
// are the freshest possible connection.
self.shared
.lock()
.active_connections
.insert(client_addr.clone(), muxer.clone());
// A new substream is ready. // A new substream is ready.
let mut new_next = muxer.clone().inbound(); let mut new_next = muxer.clone().inbound();
self.connections self.connections
@ -366,7 +378,11 @@ where
for n in (0..lock.next_incoming.len()).rev() { for n in (0..lock.next_incoming.len()).rev() {
let (muxer, mut future, addr) = lock.next_incoming.swap_remove(n); let (muxer, mut future, addr) = lock.next_incoming.swap_remove(n);
match future.poll() { match future.poll() {
Ok(Async::Ready(value)) => { Ok(Async::Ready(None)) => {
debug!(target: "libp2p-swarm", "no inbound substream for {}", addr);
lock.active_connections.remove(&addr);
}
Ok(Async::Ready(Some(value))) => {
// A substream is ready ; push back the muxer for the next time this function // A substream is ready ; push back the muxer for the next time this function
// is called, then return. // is called, then return.
debug!(target: "libp2p-swarm", "New incoming substream"); debug!(target: "libp2p-swarm", "New incoming substream");

View File

@ -102,56 +102,79 @@ where
B: StreamMuxer, B: StreamMuxer,
{ {
type Substream = EitherSocket<A::Substream, B::Substream>; type Substream = EitherSocket<A::Substream, B::Substream>;
type InboundSubstream = EitherTransportFuture<A::InboundSubstream, B::InboundSubstream>; type InboundSubstream = EitherInbound<A, B>;
type OutboundSubstream = EitherTransportFuture<A::OutboundSubstream, B::OutboundSubstream>; type OutboundSubstream = EitherOutbound<A, B>;
#[inline] #[inline]
fn inbound(self) -> Self::InboundSubstream { fn inbound(self) -> Self::InboundSubstream {
match self { match self {
EitherSocket::First(a) => EitherTransportFuture::First(a.inbound()), EitherSocket::First(a) => EitherInbound::A(a.inbound()),
EitherSocket::Second(b) => EitherTransportFuture::Second(b.inbound()), EitherSocket::Second(b) => EitherInbound::B(b.inbound()),
} }
} }
#[inline] #[inline]
fn outbound(self) -> Self::OutboundSubstream { fn outbound(self) -> Self::OutboundSubstream {
match self { match self {
EitherSocket::First(a) => EitherTransportFuture::First(a.outbound()), EitherSocket::First(a) => EitherOutbound::A(a.outbound()),
EitherSocket::Second(b) => EitherTransportFuture::Second(b.outbound()), EitherSocket::Second(b) => EitherOutbound::B(b.outbound()),
} }
} }
} }
/// Implements `Future` and redirects calls to either `First` or `Second`.
///
/// Additionally, the output will be wrapped inside a `EitherSocket`.
// TODO: This type is needed because of the lack of `impl Trait` in stable Rust.
// If Rust had impl Trait we could use the Either enum from the futures crate and add some
// modifiers to it. This custom enum is a combination of Either and these modifiers.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum EitherTransportFuture<A, B> { pub enum EitherInbound<A: StreamMuxer, B: StreamMuxer> {
First(A), A(A::InboundSubstream),
Second(B), B(B::InboundSubstream),
} }
impl<A, B> Future for EitherTransportFuture<A, B> impl<A, B> Future for EitherInbound<A, B>
where where
A: Future<Error = IoError>, A: StreamMuxer,
B: Future<Error = IoError>, B: StreamMuxer,
{ {
type Item = EitherSocket<A::Item, B::Item>; type Item = Option<EitherSocket<A::Substream, B::Substream>>;
type Error = IoError; type Error = IoError;
#[inline] #[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self { match *self {
&mut EitherTransportFuture::First(ref mut a) => { EitherInbound::A(ref mut a) => {
let item = try_ready!(a.poll()); let item = try_ready!(a.poll());
Ok(Async::Ready(EitherSocket::First(item))) Ok(Async::Ready(item.map(EitherSocket::First)))
} }
&mut EitherTransportFuture::Second(ref mut b) => { EitherInbound::B(ref mut b) => {
let item = try_ready!(b.poll()); let item = try_ready!(b.poll());
Ok(Async::Ready(EitherSocket::Second(item))) Ok(Async::Ready(item.map(EitherSocket::Second)))
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum EitherOutbound<A: StreamMuxer, B: StreamMuxer> {
A(A::OutboundSubstream),
B(B::OutboundSubstream),
}
impl<A, B> Future for EitherOutbound<A, B>
where
A: StreamMuxer,
B: StreamMuxer,
{
type Item = Option<EitherSocket<A::Substream, B::Substream>>;
type Error = IoError;
#[inline]
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match *self {
EitherOutbound::A(ref mut a) => {
let item = try_ready!(a.poll());
Ok(Async::Ready(item.map(EitherSocket::First)))
}
EitherOutbound::B(ref mut b) => {
let item = try_ready!(b.poll());
Ok(Async::Ready(item.map(EitherSocket::Second)))
} }
} }
} }

View File

@ -29,10 +29,18 @@ use tokio_io::{AsyncRead, AsyncWrite};
pub trait StreamMuxer { pub trait StreamMuxer {
/// Type of the object that represents the raw substream where data can be read and written. /// Type of the object that represents the raw substream where data can be read and written.
type Substream: AsyncRead + AsyncWrite; type Substream: AsyncRead + AsyncWrite;
/// Future that will be resolved when a new incoming substream is open. /// Future that will be resolved when a new incoming substream is open.
type InboundSubstream: Future<Item = Self::Substream, Error = IoError>; ///
/// A `None` item signals that the underlying resource has been exhausted and
/// no more substreams can be created.
type InboundSubstream: Future<Item = Option<Self::Substream>, Error = IoError>;
/// Future that will be resolved when the outgoing substream is open. /// Future that will be resolved when the outgoing substream is open.
type OutboundSubstream: Future<Item = Self::Substream, Error = IoError>; ///
/// A `None` item signals that the underlying resource has been exhausted and
/// no more substreams can be created.
type OutboundSubstream: Future<Item = Option<Self::Substream>, Error = IoError>;
/// Produces a future that will be resolved when a new incoming substream arrives. /// Produces a future that will be resolved when a new incoming substream arrives.
fn inbound(self) -> Self::InboundSubstream; fn inbound(self) -> Self::InboundSubstream;

View File

@ -115,7 +115,7 @@ fn client_to_server_outbound() {
.dial(rx.recv().unwrap()) .dial(rx.recv().unwrap())
.unwrap() .unwrap()
.and_then(|client| client.0.outbound()) .and_then(|client| client.0.outbound())
.map(|server| Framed::<_, BytesMut>::new(server)) .map(|server| Framed::<_, BytesMut>::new(server.unwrap()))
.and_then(|server| server.send("hello world".into())) .and_then(|server| server.send("hello world".into()))
.map(|_| ()); .map(|_| ());
@ -229,7 +229,7 @@ fn use_opened_listen_to_dial() {
let c2 = c.clone(); let c2 = c.clone();
c.clone().inbound().map(move |i| (c2, i)) c.clone().inbound().map(move |i| (c2, i))
}) })
.map(|(muxer, client)| (muxer, Framed::<_, BytesMut>::new(client))) .map(|(muxer, client)| (muxer, Framed::<_, BytesMut>::new(client.unwrap())))
.and_then(|(muxer, client)| { .and_then(|(muxer, client)| {
client client
.into_future() .into_future()
@ -241,7 +241,7 @@ fn use_opened_listen_to_dial() {
assert_eq!(msg, "hello world"); assert_eq!(msg, "hello world");
muxer.outbound() muxer.outbound()
}) })
.map(|client| Framed::<_, BytesMut>::new(client)) .map(|client| Framed::<_, BytesMut>::new(client.unwrap()))
.and_then(|client| client.into_future().map_err(|(err, _)| err)) .and_then(|client| client.into_future().map_err(|(err, _)| err))
.and_then(|(msg, _)| { .and_then(|(msg, _)| {
let msg = msg.unwrap(); let msg = msg.unwrap();