libp2p-ping improvements. (#1049)

* libp2p-ping improvements.

  * re #950: Removes use of the `OneShotHandler`, but still sending each
    ping over a new substream, as seems to be intentional since #828.

  * re #842: Adds an integration test that exercises the ping behaviour through
    a Swarm, requiring the RTT to be below a threshold. This requires disabling
    Nagle's algorithm as it can interact badly with delayed ACKs (and has been
    observed to do so in the context of the new ping example and integration test).

  * re #864: Control of the inbound and outbound (sub)stream protocol upgrade
    timeouts has been moved from the `NodeHandlerWrapperBuilder` to the
    `ProtocolsHandler`. That may also alleviate the need for a custom timeout
    on an `OutboundSubstreamRequest` as a `ProtocolsHandler` is now free to
    adjust these timeouts over time.

Other changes:

  * A new ping example.
  * Documentation improvements.

* More documentation improvements.

* Add PingPolicy and ensure no event is dropped.

* Remove inbound_timeout/outbound_timeout.

As per review comment, the inbound timeout is now configured
as part of the `listen_protocol` and the outbound timeout as
part of the `OutboundSubstreamRequest`.

* Simplify and generalise.

Generalise `ListenProtocol` to `SubstreamProtocol`, reusing it in
the context of `ProtocolsHandlerEvent::OutboundSubstreamRequest`.

* Doc comments for SubstreamProtocol.

* Adapt to changes in master.

* Relax upper bound for ping integration test rtt.

For "slow" CI build machines?
This commit is contained in:
Roman Borschel
2019-04-16 15:57:29 +02:00
committed by GitHub
parent 9b6336672b
commit bee5c58b27
22 changed files with 897 additions and 382 deletions

View File

@ -18,65 +18,74 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//! Handles the `/ipfs/ping/1.0.0` protocol. This allows pinging a remote node and waiting for an
//! answer.
//! This module implements the `/ipfs/ping/1.0.0` protocol.
//!
//! The ping protocol can be used as an application-layer keep-alive functionality
//! for connections of any [`Transport`] as well as to measure round-trip times.
//!
//! # Usage
//!
//! The `Ping` struct implements the `NetworkBehaviour` trait. When used, it will automatically
//! send a periodic ping to nodes we are connected to. If a remote doesn't answer in time, it gets
//! automatically disconnected.
//! The [`Ping`] struct implements the [`NetworkBehaviour`] trait. When used with a [`Swarm`],
//! it will respond to inbound ping requests and as necessary periodically send outbound
//! ping requests on every established connection. If no pings are received or
//! successfully sent within a configurable time window, [`PingHandler::connection_keep_alive`]
//! eventually indicates to the `Swarm` that the connection should be closed.
//!
//! The `Ping` struct is also what handles answering to the pings sent by remotes.
//! The `Ping` network behaviour produces [`PingEvent`]s, which may be consumed from the `Swarm`
//! by an application, e.g. to collect statistics.
//!
//! When a ping succeeds, a `PingSuccess` event is generated, indicating the time the ping took.
//! [`Swarm`]: libp2p_core::Swarm
//! [`Transport`]: libp2p_core::Transport
pub mod protocol;
pub mod handler;
pub use handler::{PingConfig, PingPolicy, PingResult, PingSuccess, PingFailure};
use handler::PingHandler;
use futures::prelude::*;
use libp2p_core::swarm::{ConnectedPoint, NetworkBehaviour, NetworkBehaviourAction, PollParameters};
use libp2p_core::protocols_handler::ProtocolsHandler;
use libp2p_core::{Multiaddr, PeerId};
use std::{marker::PhantomData, time::Duration};
use std::collections::VecDeque;
use std::marker::PhantomData;
use tokio_io::{AsyncRead, AsyncWrite};
use void::Void;
/// Network behaviour that handles receiving pings sent by other nodes and periodically pings the
/// nodes we are connected to.
/// `Ping` is a [`NetworkBehaviour`] that responds to inbound pings and
/// periodically sends outbound pings on every established connection.
///
/// See the crate root documentation for more information.
pub struct Ping<TSubstream> {
/// Marker to pin the generics.
marker: PhantomData<TSubstream>,
/// Queue of events to report to the user.
events: Vec<PingEvent>,
/// Configuration for outbound pings.
config: PingConfig,
/// Queue of events to yield to the swarm.
events: VecDeque<PingEvent>,
_marker: PhantomData<TSubstream>,
}
/// Event generated by the `Ping` behaviour.
pub enum PingEvent {
/// We have successfully pinged a peer we are connected to.
PingSuccess {
/// Id of the peer that we pinged.
peer: PeerId,
/// Time elapsed between when we sent the ping and when we received the response.
time: Duration,
}
/// Event generated by the `Ping` network behaviour.
#[derive(Debug)]
pub struct PingEvent {
/// The peer ID of the remote.
pub peer: PeerId,
/// The result of an inbound or outbound ping.
pub result: PingResult,
}
impl<TSubstream> Ping<TSubstream> {
/// Creates a `Ping`.
pub fn new() -> Self {
/// Creates a new `Ping` network behaviour with the given configuration.
pub fn new(config: PingConfig) -> Self {
Ping {
marker: PhantomData,
events: Vec::new(),
config,
events: VecDeque::new(),
_marker: PhantomData,
}
}
}
impl<TSubstream> Default for Ping<TSubstream> {
#[inline]
fn default() -> Self {
Ping::new()
Ping::new(PingConfig::new())
}
}
@ -84,11 +93,11 @@ impl<TSubstream> NetworkBehaviour for Ping<TSubstream>
where
TSubstream: AsyncRead + AsyncWrite,
{
type ProtocolsHandler = handler::PingHandler<TSubstream>;
type ProtocolsHandler = PingHandler<TSubstream>;
type OutEvent = PingEvent;
fn new_handler(&mut self) -> Self::ProtocolsHandler {
handler::PingHandler::default()
PingHandler::new(self.config.clone())
}
fn addresses_of_peer(&mut self, _peer_id: &PeerId) -> Vec<Multiaddr> {
@ -99,32 +108,16 @@ where
fn inject_disconnected(&mut self, _: &PeerId, _: ConnectedPoint) {}
fn inject_node_event(
&mut self,
source: PeerId,
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) {
if let protocol::PingOutput::Ping(time) = event {
self.events.push(PingEvent::PingSuccess {
peer: source,
time,
})
}
fn inject_node_event(&mut self, peer: PeerId, result: PingResult) {
self.events.push_front(PingEvent { peer, result })
}
fn poll(
&mut self,
_: &mut PollParameters<'_>,
) -> Async<
NetworkBehaviourAction<
<Self::ProtocolsHandler as ProtocolsHandler>::InEvent,
Self::OutEvent,
>,
> {
if !self.events.is_empty() {
return Async::Ready(NetworkBehaviourAction::GenerateEvent(self.events.remove(0)));
fn poll(&mut self, _: &mut PollParameters<'_>) -> Async<NetworkBehaviourAction<Void, PingEvent>>
{
if let Some(e) = self.events.pop_back() {
Async::Ready(NetworkBehaviourAction::GenerateEvent(e))
} else {
Async::NotReady
}
Async::NotReady
}
}