use std::{
future::Future,
io,
net::SocketAddr,
sync::{OnceLock, atomic::Ordering},
time::Duration,
};
use tokio::{
sync::{mpsc, oneshot},
task::JoinSet,
time::timeout,
};
use crate::{
connections::{Connection, DisconnectOrigin},
node::{Node, NodeTask},
protocols::{on_connect::OnConnectBundle, on_disconnect::OnDisconnectBundle},
};
mod handshake;
mod on_connect;
mod on_disconnect;
mod reading;
mod writing;
pub use handshake::Handshake;
pub use on_connect::OnConnect;
pub use on_disconnect::OnDisconnect;
pub use reading::Reading;
pub use writing::Writing;
#[derive(Default)]
pub(crate) struct Protocols {
pub(crate) handshake: OnceLock<ProtocolHandler<Connection, io::Result<Connection>>>,
pub(crate) reading: OnceLock<ProtocolHandler<Connection, io::Result<Connection>>>,
pub(crate) writing: OnceLock<writing::WritingHandler>,
pub(crate) on_connect: OnceLock<ProtocolHandler<(SocketAddr, u64), OnConnectBundle>>,
pub(crate) on_disconnect:
OnceLock<ProtocolHandler<(SocketAddr, DisconnectOrigin), OnDisconnectBundle>>,
}
pub(crate) type ReturnableItem<T, U> = (T, oneshot::Sender<U>);
pub(crate) type ReturnableConnection = ReturnableItem<Connection, io::Result<Connection>>;
pub(crate) struct ProtocolHandler<T, U>(mpsc::Sender<ReturnableItem<T, U>>);
impl<T, U> ProtocolHandler<T, U> {
pub(crate) async fn closed(&self) {
self.0.closed().await;
}
}
pub(crate) async fn await_handler_response<U>(
mut receiver: oneshot::Receiver<U>,
handler_closed: impl Future<Output = ()>,
) -> Option<U> {
tokio::select! {
biased;
res = &mut receiver => res.ok(),
_ = handler_closed => {
timeout(HANDLER_DRAIN_GRACE, &mut receiver)
.await
.ok()
.and_then(|res| res.ok())
}
}
}
const HANDLER_DRAIN_GRACE: Duration = Duration::from_secs(1);
pub(crate) trait Protocol<T, U> {
async fn trigger(&self, item: ReturnableItem<T, U>);
}
impl<T, U> Protocol<T, U> for ProtocolHandler<T, U> {
async fn trigger(&self, item: ReturnableItem<T, U>) {
let _ = self.0.send(item).await;
}
}
pub(crate) struct DisconnectOnDrop {
pub(crate) node: Option<Node>,
pub(crate) addr: SocketAddr,
pub(crate) conn_id: u64,
pub(crate) origin: DisconnectOrigin,
}
impl DisconnectOnDrop {
pub(crate) fn new(
node: Node,
addr: SocketAddr,
conn_id: u64,
origin: DisconnectOrigin,
) -> Self {
Self {
node: Some(node),
addr,
conn_id,
origin,
}
}
}
impl Drop for DisconnectOnDrop {
fn drop(&mut self) {
if let Some(node) = self.node.take() {
let (addr, conn_id, origin) = (self.addr, self.conn_id, self.origin);
let needs_recovery = node
.connections
.active
.read()
.get(&addr)
.is_some_and(|c| c.id == conn_id && !c.disconnecting.load(Ordering::Acquire));
if needs_recovery && let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.spawn(
async move { node.disconnect_w_origin(addr, origin, Some(conn_id)).await },
);
}
}
}
}
pub(crate) fn log_setup_join(
span: &tracing::Span,
protocol: &'static str,
res: Option<Result<(), tokio::task::JoinError>>,
) {
if let Some(Err(e)) = res
&& e.is_panic()
{
tracing::error!(parent: span, "a {protocol} setup task panicked: {e}");
}
}
pub(crate) async fn install_protocol_handler<H, F>(
node: &Node,
kind: NodeTask,
protocol: &'static str,
slot: impl Fn(&Protocols) -> &OnceLock<H>,
handler: H,
handler_loop: F,
) where
F: Future<Output = ()> + Send + 'static,
{
assert!(
slot(&node.protocols).get().is_none(),
"the {protocol} protocol was enabled more than once!"
);
let (tx, rx) = oneshot::channel();
let node_clone = node.clone();
let task = tokio::spawn(async move {
tracing::trace!(parent: node_clone.span(), "spawned the {protocol} handler task");
if tx.send(()).is_err() {
tracing::error!(parent: node_clone.span(), "{protocol} handler creation interrupted! shutting down the node");
node_clone.shut_down().await;
return;
}
handler_loop.await;
});
let _ = rx.await;
if node.register_task(kind, task).is_err() {
tracing::trace!("the node shut down before the {protocol} protocol could be enabled");
return;
}
assert!(
slot(&node.protocols).set(handler).is_ok(),
"the {protocol} protocol was enabled more than once!"
);
}
pub(crate) async fn run_setup_handler_loop<T: Send, U: Send>(
node: Node,
protocol: &'static str,
mut receiver: mpsc::Receiver<ReturnableItem<T, U>>,
mut spawn_setup: impl FnMut(ReturnableItem<T, U>, &mut JoinSet<()>) + Send,
) {
let mut setup_tasks = JoinSet::new();
let mut shutdown = node.shutdown.handler_signal();
let mut draining = false;
loop {
tokio::select! {
biased;
res = setup_tasks.join_next(), if !setup_tasks.is_empty() => {
log_setup_join(node.span(), protocol, res);
}
maybe_item = receiver.recv() => {
match maybe_item {
Some(_item) if draining => {} Some(item) => spawn_setup(item, &mut setup_tasks),
None => break, }
}
_ = shutdown.wait_for(|sig| *sig), if !draining => {
receiver.close();
draining = true;
}
}
}
}
pub(crate) async fn run_hook_handler_loop<T: Send, U: Send>(
node: Node,
mut receiver: mpsc::Receiver<ReturnableItem<T, U>>,
mut process: impl FnMut(ReturnableItem<T, U>) + Send,
) {
let mut shutdown = node.shutdown.handler_signal();
let mut draining = false;
loop {
tokio::select! {
biased;
maybe_item = receiver.recv() => {
match maybe_item {
Some(item) => process(item),
None => break, }
}
_ = shutdown.wait_for(|sig| *sig), if !draining => {
receiver.close();
draining = true;
}
}
}
}
pub(crate) fn panic_message(payload: &(dyn std::any::Any + Send)) -> &str {
payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>")
}