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
//! Marking a session busy for the length of one command.
use Arc;
use ;
use crateResult;
/// Holds a session in [`SessionState::Active`] for as long as the guard lives,
/// and returns it to [`SessionState::Idle`] when the guard is dropped.
///
/// The pair used to be written by hand — a transition to `Active` before the
/// command and one back to `Idle` after it. That is correct only for the escape
/// paths someone remembered to count, and it misses the one no branch can
/// express: an `.await` that never resumes because the future was **cancelled**.
/// A caller that hangs up before its response is written leaves axum dropping
/// the handler mid-command, and the session stayed `Active` forever — reporting
/// a command running long after it had finished, while `idle_seconds` grew
/// underneath it. Measured, not reasoned about: 44.7 s after the caller vanished
/// from a nine-second command.
///
/// A guard closes every exit through one path, including cancellation, because
/// dropping a future runs the destructors of everything it owns.
///
/// That is about the exits. It says nothing about how long the guard is *held*,
/// and the two are separate mistakes with the same symptom. A session reporting
/// a command that has already finished was seen twice for different reasons:
/// once because a cancelled handler never ran the transition back (fixed here),
/// and once because the session WebSocket held this guard across the delivery of
/// the command's output, which a consumer that stops reading can extend without
/// limit (fixed at the holder, `api::websocket::while_running`). A guard whose
/// exits are all covered still lies if its lifetime is not the thing it claims
/// to track.
pub