mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-07-31 00:41:59 +00:00
Fix connection & handler shutdown when using KeepAlive::Now
. (#1072)
* Fix connection & handler shutdown when using `KeepAlive::Now`. Delay::new(Instant::now()) is never immediately ready, resulting in `KeepAlive::Now` to have no effect, since the delay is re-created on every execution of `poll()` in the `NodeHandlerWrapper`. It can also send the node handler into a busy-loop, since every newly created Delay will trigger a task wakeup, which creates a new Delay with Instant::now(), and so forth. The use of `Delay::new(Instant::now())` for "immediate" connection shutdown is therefore removed here entirely. An important assumption is thereby that as long as the node handler non-empty `negotiating_in` and `negotiating_out`, the handler is not dependent on such a Delay for task wakeup. * Trigger CI.
This commit is contained in:
@@ -31,7 +31,7 @@ use crate::{
|
||||
}
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use std::{error, fmt, time::{Duration, Instant}};
|
||||
use std::{error, fmt, time::Duration};
|
||||
use tokio_timer::{Delay, Timeout};
|
||||
|
||||
/// Prototype for a `NodeHandlerWrapper`.
|
||||
@@ -64,7 +64,7 @@ where
|
||||
negotiating_out: Vec::new(),
|
||||
queued_dial_upgrades: Vec::new(),
|
||||
unique_dial_upgrade_id: 0,
|
||||
connection_shutdown: None,
|
||||
shutdown: Shutdown::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ where
|
||||
negotiating_out: Vec::new(),
|
||||
queued_dial_upgrades: Vec::new(),
|
||||
unique_dial_upgrade_id: 0,
|
||||
connection_shutdown: None,
|
||||
shutdown: Shutdown::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +112,26 @@ where
|
||||
queued_dial_upgrades: Vec<(u64, TProtoHandler::OutboundProtocol)>,
|
||||
/// Unique identifier assigned to each queued dial upgrade.
|
||||
unique_dial_upgrade_id: u64,
|
||||
/// When a connection has been deemed useless, will contain `Some` with a `Delay` to when it
|
||||
/// should be shut down.
|
||||
connection_shutdown: Option<Delay>,
|
||||
/// The currently planned connection & handler shutdown.
|
||||
shutdown: Shutdown,
|
||||
}
|
||||
|
||||
/// The options for a planned connection & handler shutdown.
|
||||
///
|
||||
/// A shutdown is planned anew based on the the return value of
|
||||
/// [`ProtocolsHandler::connection_keep_alive`] of the underlying handler
|
||||
/// after every invocation of [`ProtocolsHandler::poll`].
|
||||
///
|
||||
/// A planned shutdown is always postponed for as long as there are ingoing
|
||||
/// or outgoing substreams being negotiated, i.e. it is a graceful, "idle"
|
||||
/// shutdown.
|
||||
enum Shutdown {
|
||||
/// No shutdown is planned.
|
||||
None,
|
||||
/// A shut down is planned as soon as possible.
|
||||
Asap,
|
||||
/// A shut down is planned for when a `Delay` has elapsed.
|
||||
Later(Delay)
|
||||
}
|
||||
|
||||
/// Error generated by the `NodeHandlerWrapper`.
|
||||
@@ -257,10 +274,12 @@ where
|
||||
// calls on `self.handler`.
|
||||
let poll_result = self.handler.poll()?;
|
||||
|
||||
self.connection_shutdown = match self.handler.connection_keep_alive() {
|
||||
KeepAlive::Until(expiration) => Some(Delay::new(expiration)),
|
||||
KeepAlive::Now => Some(Delay::new(Instant::now())),
|
||||
KeepAlive::Forever => None,
|
||||
// Ask the handler whether it wants the connection (and the handler itself)
|
||||
// to be kept alive, which determines the planned shutdown, if any.
|
||||
self.shutdown = match self.handler.connection_keep_alive() {
|
||||
KeepAlive::Until(t) => Shutdown::Later(Delay::new(t)),
|
||||
KeepAlive::Now => Shutdown::Asap,
|
||||
KeepAlive::Forever => Shutdown::None
|
||||
};
|
||||
|
||||
match poll_result {
|
||||
@@ -282,20 +301,17 @@ where
|
||||
Async::NotReady => (),
|
||||
};
|
||||
|
||||
// Check the `connection_shutdown`.
|
||||
if let Some(mut connection_shutdown) = self.connection_shutdown.take() {
|
||||
// If we're negotiating substreams, let's delay the closing.
|
||||
if self.negotiating_in.is_empty() && self.negotiating_out.is_empty() {
|
||||
match connection_shutdown.poll() {
|
||||
Ok(Async::Ready(_)) | Err(_) => {
|
||||
return Err(NodeHandlerWrapperError::UselessTimeout);
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
self.connection_shutdown = Some(connection_shutdown);
|
||||
}
|
||||
// Check if the connection (and handler) should be shut down.
|
||||
// As long as we're still negotiating substreams, shutdown is always postponed.
|
||||
if self.negotiating_in.is_empty() && self.negotiating_out.is_empty() {
|
||||
match self.shutdown {
|
||||
Shutdown::None => {},
|
||||
Shutdown::Asap => return Err(NodeHandlerWrapperError::UselessTimeout),
|
||||
Shutdown::Later(ref mut delay) => match delay.poll() {
|
||||
Ok(Async::Ready(_)) | Err(_) =>
|
||||
return Err(NodeHandlerWrapperError::UselessTimeout),
|
||||
Ok(Async::NotReady) => {}
|
||||
}
|
||||
} else {
|
||||
self.connection_shutdown = Some(connection_shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user