Add libp2p-request-response protocol. (#1596)

* Add the libp2p-request-response protocol.

This crate provides a generic implementation for request/response
protocols, whereby each request is sent on a new substream.

* Fix OneShotHandler usage in floodsub.

* Custom ProtocolsHandler and multiple protocols.

  1. Implement a custom ProtocolsHandler instead of using
     the OneShotHandler for better control and error handling.
     In particular, all request/response sending/receiving is
     kept in the substreams upgrades and thus the background
     task of a connection.
  2. Support multiple protocols (usually protocol versions)
     with a single `RequestResponse` instance, with
     configurable inbound/outbound support.

* Small doc clarification.

* Remove unnecessary Sync bounds.

* Remove redundant Clone constraint.

* Update protocols/request-response/Cargo.toml

Co-authored-by: Toralf Wittner <tw@dtex.org>

* Update dev-dependencies.

* Update Cargo.tomls.

* Add changelog.

* Remove Sync bound from RequestResponseCodec::Protocol.

Apparently the compiler just needs some help with the scope
of borrows, which is unfortunate.

* Try async-trait.

* Allow checking whether a ResponseChannel is still open.

Also expand the commentary on `send_response` to indicate that
responses may be discard if they come in too late.

* Add `RequestResponse::is_pending`.

As an analogue of `ResponseChannel::is_open` for outbound requests.

* Revert now unnecessary changes to the OneShotHandler.

Since `libp2p-request-response` is no longer using it.

* Update CHANGELOG for libp2p-swarm.

Co-authored-by: Toralf Wittner <tw@dtex.org>
This commit is contained in:
Roman Borschel
2020-06-29 17:08:40 +02:00
committed by GitHub
parent 7270ed8721
commit eb8cb43508
15 changed files with 1481 additions and 72 deletions

View File

@ -140,7 +140,7 @@ pub trait ProtocolsHandler: Send + 'static {
/// Injects an event coming from the outside in the handler.
fn inject_event(&mut self, event: Self::InEvent);
/// Indicates to the handler that upgrading a substream to the given protocol has failed.
/// Indicates to the handler that upgrading an outbound substream to the given protocol has failed.
fn inject_dial_upgrade_error(
&mut self,
info: Self::OutboundOpenInfo,
@ -149,6 +149,14 @@ pub trait ProtocolsHandler: Send + 'static {
>
);
/// Indicates to the handler that upgrading an inbound substream to the given protocol has failed.
fn inject_listen_upgrade_error(
&mut self,
_: ProtocolsHandlerUpgrErr<
<Self::InboundProtocol as InboundUpgradeSend>::Error
>
) {}
/// Returns until when the connection should be kept alive.
///
/// This method is called by the `Swarm` after each invocation of
@ -236,7 +244,7 @@ pub struct SubstreamProtocol<TUpgrade> {
}
impl<TUpgrade> SubstreamProtocol<TUpgrade> {
/// Create a new `ListenProtocol` from the given upgrade.
/// Create a new `SubstreamProtocol` from the given upgrade.
///
/// The default timeout for applying the given upgrade on a substream is
/// 10 seconds.

View File

@ -228,15 +228,26 @@ where
for n in (0..self.negotiating_in.len()).rev() {
let (mut in_progress, mut timeout) = self.negotiating_in.swap_remove(n);
match Future::poll(Pin::new(&mut timeout), cx) {
Poll::Ready(_) => continue,
Poll::Ready(Ok(_)) => {
let err = ProtocolsHandlerUpgrErr::Timeout;
self.handler.inject_listen_upgrade_error(err);
continue
}
Poll::Ready(Err(_)) => {
let err = ProtocolsHandlerUpgrErr::Timer;
self.handler.inject_listen_upgrade_error(err);
continue;
}
Poll::Pending => {},
}
match Future::poll(Pin::new(&mut in_progress), cx) {
Poll::Ready(Ok(upgrade)) =>
self.handler.inject_fully_negotiated_inbound(upgrade),
Poll::Pending => self.negotiating_in.push((in_progress, timeout)),
// TODO: return a diagnostic event?
Poll::Ready(Err(_err)) => {}
Poll::Ready(Err(err)) => {
let err = ProtocolsHandlerUpgrErr::Upgrade(err);
self.handler.inject_listen_upgrade_error(err);
}
}
}

View File

@ -31,23 +31,20 @@ use smallvec::SmallVec;
use std::{error, task::Context, task::Poll, time::Duration};
use wasm_timer::Instant;
/// Implementation of `ProtocolsHandler` that opens a new substream for each individual message.
///
/// This struct is meant to be a helper for other implementations to use.
/// A `ProtocolsHandler` that opens a new substream for each request.
// TODO: Debug
pub struct OneShotHandler<TInProto, TOutProto, TOutEvent>
pub struct OneShotHandler<TInbound, TOutbound, TEvent>
where
TOutProto: OutboundUpgradeSend,
TOutbound: OutboundUpgradeSend,
{
/// The upgrade for inbound substreams.
listen_protocol: SubstreamProtocol<TInProto>,
listen_protocol: SubstreamProtocol<TInbound>,
/// If `Some`, something bad happened and we should shut down the handler with an error.
pending_error:
Option<ProtocolsHandlerUpgrErr<<TOutProto as OutboundUpgradeSend>::Error>>,
pending_error: Option<ProtocolsHandlerUpgrErr<<TOutbound as OutboundUpgradeSend>::Error>>,
/// Queue of events to produce in `poll()`.
events_out: SmallVec<[TOutEvent; 4]>,
events_out: SmallVec<[TEvent; 4]>,
/// Queue of outbound substreams to open.
dial_queue: SmallVec<[TOutProto; 4]>,
dial_queue: SmallVec<[TOutbound; 4]>,
/// Current number of concurrent outbound substreams being opened.
dial_negotiated: u32,
/// Maximum number of concurrent outbound substreams being opened. Value is never modified.
@ -58,15 +55,14 @@ where
config: OneShotHandlerConfig,
}
impl<TInProto, TOutProto, TOutEvent>
OneShotHandler<TInProto, TOutProto, TOutEvent>
impl<TInbound, TOutbound, TEvent>
OneShotHandler<TInbound, TOutbound, TEvent>
where
TOutProto: OutboundUpgradeSend,
TOutbound: OutboundUpgradeSend,
{
/// Creates a `OneShotHandler`.
#[inline]
pub fn new(
listen_protocol: SubstreamProtocol<TInProto>,
listen_protocol: SubstreamProtocol<TInbound>,
config: OneShotHandlerConfig,
) -> Self {
OneShotHandler {
@ -77,12 +73,11 @@ where
dial_negotiated: 0,
max_dial_negotiated: 8,
keep_alive: KeepAlive::Yes,
config
config,
}
}
/// Returns the number of pending requests.
#[inline]
pub fn pending_requests(&self) -> u32 {
self.dial_negotiated + self.dial_queue.len() as u32
}
@ -91,8 +86,7 @@ where
///
/// > **Note**: If you modify the protocol, modifications will only applies to future inbound
/// > substreams, not the ones already being negotiated.
#[inline]
pub fn listen_protocol_ref(&self) -> &SubstreamProtocol<TInProto> {
pub fn listen_protocol_ref(&self) -> &SubstreamProtocol<TInbound> {
&self.listen_protocol
}
@ -100,26 +94,23 @@ where
///
/// > **Note**: If you modify the protocol, modifications will only applies to future inbound
/// > substreams, not the ones already being negotiated.
#[inline]
pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol<TInProto> {
pub fn listen_protocol_mut(&mut self) -> &mut SubstreamProtocol<TInbound> {
&mut self.listen_protocol
}
/// Opens an outbound substream with `upgrade`.
#[inline]
pub fn send_request(&mut self, upgrade: TOutProto) {
pub fn send_request(&mut self, upgrade: TOutbound) {
self.keep_alive = KeepAlive::Yes;
self.dial_queue.push(upgrade);
}
}
impl<TInProto, TOutProto, TOutEvent> Default
for OneShotHandler<TInProto, TOutProto, TOutEvent>
impl<TInbound, TOutbound, TEvent> Default
for OneShotHandler<TInbound, TOutbound, TEvent>
where
TOutProto: OutboundUpgradeSend,
TInProto: InboundUpgradeSend + Default,
TOutbound: OutboundUpgradeSend,
TInbound: InboundUpgradeSend + Default,
{
#[inline]
fn default() -> Self {
OneShotHandler::new(
SubstreamProtocol::new(Default::default()),
@ -128,45 +119,42 @@ where
}
}
impl<TInProto, TOutProto, TOutEvent> ProtocolsHandler
for OneShotHandler<TInProto, TOutProto, TOutEvent>
impl<TInbound, TOutbound, TEvent> ProtocolsHandler
for OneShotHandler<TInbound, TOutbound, TEvent>
where
TInProto: InboundUpgradeSend + Send + 'static,
TOutProto: OutboundUpgradeSend,
TInProto::Output: Into<TOutEvent>,
TOutProto::Output: Into<TOutEvent>,
TOutProto::Error: error::Error + Send + 'static,
SubstreamProtocol<TInProto>: Clone,
TOutEvent: Send + 'static,
TInbound: InboundUpgradeSend + Send + 'static,
TOutbound: OutboundUpgradeSend,
TInbound::Output: Into<TEvent>,
TOutbound::Output: Into<TEvent>,
TOutbound::Error: error::Error + Send + 'static,
SubstreamProtocol<TInbound>: Clone,
TEvent: Send + 'static,
{
type InEvent = TOutProto;
type OutEvent = TOutEvent;
type InEvent = TOutbound;
type OutEvent = TEvent;
type Error = ProtocolsHandlerUpgrErr<
<Self::OutboundProtocol as OutboundUpgradeSend>::Error,
>;
type InboundProtocol = TInProto;
type OutboundProtocol = TOutProto;
type InboundProtocol = TInbound;
type OutboundProtocol = TOutbound;
type OutboundOpenInfo = ();
#[inline]
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
self.listen_protocol.clone()
}
#[inline]
fn inject_fully_negotiated_inbound(
&mut self,
out: <Self::InboundProtocol as InboundUpgradeSend>::Output,
) {
// If we're shutting down the connection for inactivity, reset the timeout.
if !self.keep_alive.is_yes() {
self.keep_alive = KeepAlive::Until(Instant::now() + self.config.inactive_timeout);
self.keep_alive = KeepAlive::Until(Instant::now() + self.config.keep_alive_timeout);
}
self.events_out.push(out.into());
}
#[inline]
fn inject_fully_negotiated_outbound(
&mut self,
out: <Self::OutboundProtocol as OutboundUpgradeSend>::Output,
@ -175,21 +163,19 @@ where
self.dial_negotiated -= 1;
if self.dial_negotiated == 0 && self.dial_queue.is_empty() {
self.keep_alive = KeepAlive::Until(Instant::now() + self.config.inactive_timeout);
self.keep_alive = KeepAlive::Until(Instant::now() + self.config.keep_alive_timeout);
}
self.events_out.push(out.into());
}
#[inline]
fn inject_event(&mut self, event: Self::InEvent) {
self.send_request(event);
}
#[inline]
fn inject_dial_upgrade_error(
&mut self,
_: Self::OutboundOpenInfo,
_info: Self::OutboundOpenInfo,
error: ProtocolsHandlerUpgrErr<
<Self::OutboundProtocol as OutboundUpgradeSend>::Error,
>,
@ -199,7 +185,6 @@ where
}
}
#[inline]
fn connection_keep_alive(&self) -> KeepAlive {
self.keep_alive
}
@ -211,12 +196,12 @@ where
ProtocolsHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent, Self::Error>,
> {
if let Some(err) = self.pending_error.take() {
return Poll::Ready(ProtocolsHandlerEvent::Close(err));
return Poll::Ready(ProtocolsHandlerEvent::Close(err))
}
if !self.events_out.is_empty() {
return Poll::Ready(ProtocolsHandlerEvent::Custom(
self.events_out.remove(0),
self.events_out.remove(0)
));
} else {
self.events_out.shrink_to_fit();
@ -225,10 +210,11 @@ where
if !self.dial_queue.is_empty() {
if self.dial_negotiated < self.max_dial_negotiated {
self.dial_negotiated += 1;
let upgrade = self.dial_queue.remove(0);
return Poll::Ready(
ProtocolsHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(self.dial_queue.remove(0))
.with_timeout(self.config.substream_timeout),
protocol: SubstreamProtocol::new(upgrade)
.with_timeout(self.config.outbound_substream_timeout),
info: (),
},
);
@ -244,17 +230,18 @@ where
/// Configuration parameters for the `OneShotHandler`
#[derive(Debug)]
pub struct OneShotHandlerConfig {
/// After the given duration has elapsed, an inactive connection will shutdown.
pub inactive_timeout: Duration,
/// Timeout duration for each newly opened outbound substream.
pub substream_timeout: Duration,
/// Keep-alive timeout for idle connections.
pub keep_alive_timeout: Duration,
/// Timeout for outbound substream upgrades.
pub outbound_substream_timeout: Duration,
}
impl Default for OneShotHandlerConfig {
fn default() -> Self {
let inactive_timeout = Duration::from_secs(10);
let substream_timeout = Duration::from_secs(10);
OneShotHandlerConfig { inactive_timeout, substream_timeout }
OneShotHandlerConfig {
keep_alive_timeout: Duration::from_secs(10),
outbound_substream_timeout: Duration::from_secs(10),
}
}
}