feat(swarm): Make executor for connection tasks explicit (#3097)

Previously, the executor for connection tasks silently defaulted to a `futures::executor::ThreadPool`. This causes issues such as https://github.com/libp2p/rust-libp2p/issues/2230.

With this patch, we force the user to choose, which executor they want to run the connection tasks on which results in overall simpler API with less footguns.

Closes #3068.
This commit is contained in:
Hannes
2022-11-15 15:26:03 +01:00
committed by GitHub
parent d8fe7bf49f
commit d5ea93dd71
41 changed files with 384 additions and 181 deletions

View File

@ -64,6 +64,7 @@ mod upgrade;
pub mod behaviour;
pub mod dial_opts;
pub mod dummy;
mod executor;
pub mod handler;
pub mod keep_alive;
@ -94,6 +95,7 @@ pub use connection::{
ConnectionError, ConnectionLimit, PendingConnectionError, PendingInboundConnectionError,
PendingOutboundConnectionError,
};
pub use executor::Executor;
pub use handler::{
ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerSelect, ConnectionHandlerUpgrErr,
IntoConnectionHandler, IntoConnectionHandlerSelect, KeepAlive, OneShotHandler,
@ -117,7 +119,7 @@ use libp2p_core::{
muxing::StreamMuxerBox,
transport::{self, ListenerId, TransportError, TransportEvent},
upgrade::ProtocolName,
Endpoint, Executor, Multiaddr, Negotiated, PeerId, Transport,
Endpoint, Multiaddr, Negotiated, PeerId, Transport,
};
use registry::{AddressIntoIter, Addresses};
use smallvec::SmallVec;
@ -328,12 +330,89 @@ where
TBehaviour: NetworkBehaviour,
{
/// Builds a new `Swarm`.
#[deprecated(
since = "0.41.0",
note = "This constructor is considered ambiguous regarding the executor. Use one of the new, executor-specific constructors or `Swarm::with_threadpool_executor` for the same behaviour."
)]
pub fn new(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
SwarmBuilder::new(transport, behaviour, local_peer_id).build()
Self::with_threadpool_executor(transport, behaviour, local_peer_id)
}
/// Builds a new `Swarm` with a provided executor.
pub fn with_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
executor: impl Executor + Send + 'static,
) -> Self {
SwarmBuilder::with_executor(transport, behaviour, local_peer_id, executor).build()
}
/// Builds a new `Swarm` with a tokio executor.
#[cfg(feature = "tokio")]
pub fn with_tokio_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
Self::with_executor(
transport,
behaviour,
local_peer_id,
crate::executor::TokioExecutor,
)
}
/// Builds a new `Swarm` with an async-std executor.
#[cfg(feature = "async-std")]
pub fn with_async_std_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
Self::with_executor(
transport,
behaviour,
local_peer_id,
crate::executor::AsyncStdExecutor,
)
}
/// Builds a new `Swarm` with a threadpool executor.
pub fn with_threadpool_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
let builder = match ThreadPoolBuilder::new()
.name_prefix("libp2p-swarm-task-")
.create()
{
Ok(tp) => SwarmBuilder::with_executor(transport, behaviour, local_peer_id, tp),
Err(err) => {
log::warn!("Failed to create executor thread pool: {:?}", err);
SwarmBuilder::without_executor(transport, behaviour, local_peer_id)
}
};
builder.build()
}
/// Builds a new `Swarm` without an executor, instead using the current task.
///
/// ## ⚠️ Performance warning
/// All connections will be polled on the current task, thus quite bad performance
/// characteristics should be expected. Whenever possible use an executor and
/// [`Swarm::with_executor`].
pub fn without_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
SwarmBuilder::without_executor(transport, behaviour, local_peer_id).build()
}
/// Returns information about the connections underlying the [`Swarm`].
@ -1294,16 +1373,67 @@ where
/// Creates a new `SwarmBuilder` from the given transport, behaviour and
/// local peer ID. The `Swarm` with its underlying `Network` is obtained
/// via [`SwarmBuilder::build`].
#[deprecated(
since = "0.41.0",
note = "Use `SwarmBuilder::with_executor` or `SwarmBuilder::without_executor` instead."
)]
pub fn new(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
let executor: Option<Box<dyn Executor + Send>> = match ThreadPoolBuilder::new()
.name_prefix("libp2p-swarm-task-")
.create()
.ok()
{
Some(tp) => Some(Box::new(tp)),
None => None,
};
SwarmBuilder {
local_peer_id,
transport,
behaviour,
pool_config: Default::default(),
pool_config: PoolConfig::new(executor),
connection_limits: Default::default(),
}
}
/// Creates a new [`SwarmBuilder`] from the given transport, behaviour, local peer ID and
/// executor. The `Swarm` with its underlying `Network` is obtained via
/// [`SwarmBuilder::build`].
pub fn with_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
executor: impl Executor + Send + 'static,
) -> Self {
Self {
local_peer_id,
transport,
behaviour,
pool_config: PoolConfig::new(Some(Box::new(executor))),
connection_limits: Default::default(),
}
}
/// Creates a new [`SwarmBuilder`] from the given transport, behaviour and local peer ID. The
/// `Swarm` with its underlying `Network` is obtained via [`SwarmBuilder::build`].
///
/// ## ⚠️ Performance warning
/// All connections will be polled on the current task, thus quite bad performance
/// characteristics should be expected. Whenever possible use an executor and
/// [`SwarmBuilder::with_executor`].
pub fn without_executor(
transport: transport::Boxed<(PeerId, StreamMuxerBox)>,
behaviour: TBehaviour,
local_peer_id: PeerId,
) -> Self {
Self {
local_peer_id,
transport,
behaviour,
pool_config: PoolConfig::new(None),
connection_limits: Default::default(),
}
}
@ -1313,8 +1443,9 @@ where
/// By default, unless another executor has been configured,
/// [`SwarmBuilder::build`] will try to set up a
/// [`ThreadPool`](futures::executor::ThreadPool).
pub fn executor(mut self, e: Box<dyn Executor + Send>) -> Self {
self.pool_config = self.pool_config.with_executor(e);
#[deprecated(since = "0.41.0", note = "Use `SwarmBuilder::with_executor` instead.")]
pub fn executor(mut self, executor: Box<dyn Executor + Send>) -> Self {
self.pool_config = self.pool_config.with_executor(executor);
self
}
@ -1412,25 +1543,10 @@ where
.map(|info| info.protocol_name().to_vec())
.collect();
// If no executor has been explicitly configured, try to set up a thread pool.
let pool_config =
self.pool_config.or_else_with_executor(|| {
match ThreadPoolBuilder::new()
.name_prefix("libp2p-swarm-task-")
.create()
{
Ok(tp) => Some(Box::new(move |f| tp.spawn_ok(f))),
Err(err) => {
log::warn!("Failed to create executor thread pool: {:?}", err);
None
}
}
});
Swarm {
local_peer_id: self.local_peer_id,
transport: self.transport,
pool: Pool::new(self.local_peer_id, pool_config, self.connection_limits),
pool: Pool::new(self.local_peer_id, self.pool_config, self.connection_limits),
behaviour: self.behaviour,
supported_protocols,
listened_addrs: HashMap::new(),
@ -1586,6 +1702,7 @@ mod tests {
use super::*;
use crate::test::{CallTraceBehaviour, MockBehaviour};
use futures::executor::block_on;
use futures::executor::ThreadPool;
use futures::future::poll_fn;
use futures::future::Either;
use futures::{executor, future, ready};
@ -1622,7 +1739,12 @@ mod tests {
.multiplex(yamux::YamuxConfig::default())
.boxed();
let behaviour = CallTraceBehaviour::new(MockBehaviour::new(handler_proto));
SwarmBuilder::new(transport, behaviour, local_public_key.into())
match ThreadPool::new().ok() {
Some(tp) => {
SwarmBuilder::with_executor(transport, behaviour, local_public_key.into(), tp)
}
None => SwarmBuilder::without_executor(transport, behaviour, local_public_key.into()),
}
}
fn swarms_connected<TBehaviour>(