1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Cooperative shutdown for **blocking** capture loops.
//!
//! Most protocols run their accept loop as a Tokio task, so `stop_server` cancelling the
//! `JoinHandle` registered with [`AppState::register_server_task`] is enough: the task is
//! dropped at its next await point and the socket goes with it.
//!
//! The packet-capture family (`arp`, `datalink`, `icmp`, `isis`) cannot work that way. Their
//! loops are synchronous — `pcap::Capture::next_packet()` and `socket2::Socket::recv_from()`
//! block a thread from `spawn_blocking`, and **`JoinHandle::abort()` cannot interrupt a
//! blocking call**: Tokio has no way to unwind a thread parked in a syscall. Each of the four
//! also dropped its handle outright and never registered it, so on a host with the privileges
//! to actually run them (root, `/dev/bpf*` on macOS/BSD, `CAP_NET_RAW` on Linux) `stop_server`
//! left the capture running until the process exited — still capturing, still feeding the LLM,
//! while the operator believed it had stopped.
//!
//! [`StopSignal`] is the mechanism that fixes that. It is an `AtomicBool` the blocking loop
//! polls between packets, plus a Tokio task that trips it when cancelled, so the *existing*
//! `register_server_task` / `remove_server` plumbing drives it with no new API:
//!
//! ```ignore
//! let stop = StopSignal::new();
//! let stop_in_loop = stop.clone();
//! tokio::task::spawn_blocking(move || {
//! loop {
//! if stop_in_loop.is_stopped() { break; } // ← checked every iteration
//! match cap.next_packet() { /* … */ }
//! }
//! });
//! // …after the loop reports readiness:
//! app_state.register_server_task(server_id, stop.park_task()).await;
//! ```
//!
//! # Why the read timeout matters
//!
//! A poll between packets only runs when the blocking call returns. The four callers each
//! bound that:
//!
//! * `arp`, `datalink`, `isis` open pcap with `.timeout(1000)`, so `next_packet()` returns
//! `Error::TimeoutExpired` at least once a second on an idle link — the loop then sees the
//! flag. **Shutdown latency is therefore up to ~1s.**
//! * `icmp` puts its raw socket in non-blocking mode and sleeps 10ms on `WouldBlock`, so it
//! notices within ~10ms.
//!
//! Removing the pcap read timeout would make shutdown unbounded again on an idle interface.
//! Do not.
use ;
use Arc;
/// A flag a blocking loop polls to learn that it must stop.
///
/// Cloning is cheap and every clone observes the same flag.
;
/// Trips the flag on drop. Lives inside the parked task's future.
;