edge v21.0.0, found from CIRISServer#568 while making the node's stop legible.
Edge::run(shutdown_rx) (src/edge.rs ~4359) does this:
let mut tasks = Vec::new();
for transport in &self.transports {
let t = transport.clone(); let tx = inbound_tx.clone();
tasks.push(tokio::spawn(async move {
if let Err(e) = t.listen(tx).await { tracing::error!(…, "transport listen exited"); }
}));
}
…
loop { tokio::select! { _ = shutdown.changed() => { tracing::info!("edge: shutdown signal received"); break; } … } }
for t in tasks { let _ = t.await; }
Ok(())
The outbound dispatcher and the blackhole pruner get shutdown_rx clones and stop (dispatcher: shutdown signal received is logged). The transport listen tasks get no shutdown receiver, ReticulumTransport::listen never returns on its own, and run then awaits them forever. So run logs "shutdown signal received" and never returns.
Measured on a standalone ciris-server 0.5.203 (SIGTERM at 16:07:35 UTC): read API drained in <1 ms, every server loop logged "shutting down", edge logged the two lines above at 16:07:35.1807 — and the process was alive with 38 threads eight minutes later, still admitting RNS announces (peer_admitted … 16:12:38) and ticking the trace-plane watch (16:15:04), until kill -9. In the mesh harness the same node ends docker stop with exit 137 after the 30 s grace. The server had been waiting on edge_join.await.
Ask: either hand the listener tasks the shutdown receiver (select! the listen future against shutdown.changed() and close the transport), or have run stop the transports (t.close()/shutdown()) before joining. Until then CIRISServer bounds the join (10 s, then abort(), named at ERROR) so a stop cannot hang on it — that is a mitigation, not the fix: an aborted listener does not close its sockets cleanly.
Repro without the harness: ciris-server --home <scratch>, wait for :4243/health 200, kill -TERM <pid>, watch it not exit.
🤖 Generated with Claude Code
edge v21.0.0, found from CIRISServer#568 while making the node's stop legible.
Edge::run(shutdown_rx)(src/edge.rs ~4359) does this:The outbound dispatcher and the blackhole pruner get
shutdown_rxclones and stop (dispatcher: shutdown signal receivedis logged). The transportlistentasks get no shutdown receiver,ReticulumTransport::listennever returns on its own, andrunthen awaits them forever. Sorunlogs "shutdown signal received" and never returns.Measured on a standalone ciris-server 0.5.203 (SIGTERM at 16:07:35 UTC): read API drained in <1 ms, every server loop logged "shutting down", edge logged the two lines above at 16:07:35.1807 — and the process was alive with 38 threads eight minutes later, still admitting RNS announces (
peer_admitted … 16:12:38) and ticking the trace-plane watch (16:15:04), untilkill -9. In the mesh harness the same node endsdocker stopwith exit 137 after the 30 s grace. The server had been waiting onedge_join.await.Ask: either hand the listener tasks the shutdown receiver (
select!the listen future againstshutdown.changed()and close the transport), or haverunstop the transports (t.close()/shutdown()) before joining. Until then CIRISServer bounds the join (10 s, thenabort(), named at ERROR) so a stop cannot hang on it — that is a mitigation, not the fix: an aborted listener does not close its sockets cleanly.Repro without the harness:
ciris-server --home <scratch>, wait for:4243/health200,kill -TERM <pid>, watch it not exit.🤖 Generated with Claude Code