mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-13 10:01:25 +00:00
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:
166
swarm/src/lib.rs
166
swarm/src/lib.rs
@ -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>(
|
||||
|
Reference in New Issue
Block a user