use std::{
future::Future,
net::SocketAddr,
panic::{AssertUnwindSafe, resume_unwind},
time::Duration,
};
use futures_util::FutureExt;
use tokio::{
sync::{mpsc, oneshot},
task::JoinHandle,
time::timeout,
};
use tracing::*;
#[cfg(doc)]
use crate::{
Connection, Node,
protocols::{OnConnect, Reading, Writing},
};
use crate::{
Pea2Pea,
connections::{DisconnectOrigin, create_connection_span},
node::NodeTask,
protocols::{ProtocolHandler, panic_message},
};
pub(crate) type OnDisconnectBundle = (JoinHandle<()>, oneshot::Receiver<()>);
pub trait OnDisconnect: Pea2Pea
where
Self: Clone + Send + Sync + 'static,
{
const TIMEOUT_MS: u64 = 3_000;
fn enable_on_disconnect(&self) -> impl Future<Output = ()> {
async {
assert!(
self.node().protocols.on_disconnect.get().is_none(),
"the OnDisconnect protocol was enabled more than once!"
);
let (from_node_sender, mut from_node_receiver) =
mpsc::channel::<(
(SocketAddr, DisconnectOrigin),
oneshot::Sender<OnDisconnectBundle>,
)>(self.node().config().max_connections as usize);
let (tx, rx) = oneshot::channel::<()>();
let self_clone = self.clone();
let on_disconnect_task = tokio::spawn(async move {
trace!(parent: self_clone.node().span(), "spawned the OnDisconnect handler task");
if tx.send(()).is_err() {
error!(parent: self_clone.node().span(), "OnDisconnect handler creation interrupted! shutting down the node");
self_clone.node().shut_down().await;
return;
}
while let Some(((addr, origin), notifier)) = from_node_receiver.recv().await {
let self_clone2 = self_clone.clone();
let (done_tx, done_rx) = oneshot::channel();
let handle = tokio::spawn(async move {
let hook = AssertUnwindSafe(self_clone2.on_disconnect(addr, origin))
.catch_unwind();
match timeout(Duration::from_millis(Self::TIMEOUT_MS), hook).await {
Ok(Ok(())) => {}
Ok(Err(payload)) => {
let conn_span =
create_connection_span(addr, self_clone2.node().span());
error!(parent: conn_span, "OnDisconnect::on_disconnect panicked: {}", panic_message(&*payload));
resume_unwind(payload);
}
Err(_) => {
let conn_span =
create_connection_span(addr, self_clone2.node().span());
warn!(parent: conn_span, "OnDisconnect logic timed out");
}
}
let _ = done_tx.send(());
});
if let Err((handle, _)) = notifier.send((handle, done_rx)) {
handle.abort();
}
}
});
let _ = rx.await;
if self
.node()
.register_task(NodeTask::OnDisconnect, on_disconnect_task)
.is_err()
{
trace!("the node shut down before the OnDisconnect protocol could be enabled");
return;
}
let hdl = ProtocolHandler(from_node_sender);
assert!(
self.node().protocols.on_disconnect.set(hdl).is_ok(),
"the OnDisconnect protocol was enabled more than once!"
);
}
}
fn on_disconnect(
&self,
addr: SocketAddr,
origin: DisconnectOrigin,
) -> impl Future<Output = ()> + Send;
}