use iroh::Endpoint;
use tracing::Instrument as _;
use crate::backend::TcpBackend;
use crate::credential::Credential;
use crate::exchange;
use crate::fingerprint;
use crate::lifecycle::{Lifecycle, aggregate};
use crate::peer;
use crate::peers::PeerRegistry;
pub(crate) struct ServeState {
pub(crate) endpoint: Endpoint,
pub(crate) credential: Credential,
pub(crate) backend: TcpBackend,
pub(crate) lifecycle: Lifecycle,
pub(crate) peers: PeerRegistry,
}
impl ServeState {
pub(crate) fn new(endpoint: Endpoint, credential: Credential, backend: TcpBackend) -> Self {
Self {
endpoint,
credential,
backend,
lifecycle: Lifecycle::new(),
peers: PeerRegistry::new(),
}
}
}
pub(crate) async fn accept_loop(state: std::sync::Arc<ServeState>) {
loop {
let incoming = tokio::select! {
biased;
() = state.lifecycle.wait_until_closed() => break,
incoming = state.endpoint.accept() => incoming,
};
let Some(incoming) = incoming else { break };
let state = state.clone();
tokio::spawn(async move {
match incoming.await {
Ok(connection) => serve_connection(state, connection).await,
Err(error) => tracing::debug!(%error, "a connection never established"),
}
});
}
}
async fn serve_connection(
state: std::sync::Arc<ServeState>,
connection: iroh::endpoint::Connection,
) {
let path = peer::path_of(&connection);
let peer_name: std::sync::Arc<str> = fingerprint::of(connection.remote_id().as_bytes()).into();
let peer = state.peers.add(peer_name.clone(), path, &state.lifecycle);
let slots = state.peers.slots(&peer_name);
let span = tracing::info_span!(
"peer",
peer = %peer_name,
path = aggregate(&[path]).as_str(),
);
span.in_scope(|| tracing::info!("peer connected"));
loop {
let accepted = tokio::select! {
biased;
() = state.lifecycle.wait_until_closed() => break,
accepted = connection.accept_bi() => accepted,
};
let Ok((send, recv)) = accepted else { break };
let state = state.clone();
let Ok(slot) = slots.clone().acquire_owned().await else {
break;
};
let guard = state.lifecycle.enter();
let peer_name = peer_name.clone();
tokio::spawn(
async move {
let _slot = slot;
let _guard = guard;
let mut stream = tokio::io::join(recv, send);
let _ = exchange::serve_exchange(
&mut stream,
&state.credential,
&state.backend,
&peer_name,
)
.await;
deliver(stream).await;
}
.instrument(span.clone()),
);
}
span.in_scope(|| tracing::info!("peer disconnected"));
state.peers.remove(peer, &state.lifecycle);
}
async fn deliver(stream: tokio::io::Join<iroh::endpoint::RecvStream, iroh::endpoint::SendStream>) {
let (_recv, mut send) = stream.into_inner();
let _ = send.finish();
let _ = send.stopped().await;
}
pub(crate) async fn shutdown(state: &ServeState) {
state.lifecycle.close();
state.lifecycle.wait_until_drained().await;
state.endpoint.close().await;
state.lifecycle.mark_torn_down();
}
pub(crate) async fn shutdown_timeout(state: &ServeState, grace: std::time::Duration) -> bool {
state.lifecycle.close();
let drained = tokio::time::timeout(grace, state.lifecycle.wait_until_drained())
.await
.is_ok();
state.endpoint.close().await;
state.lifecycle.mark_torn_down();
drained
}