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
//! **What a SIGTERM means to a running yog** (DESIGN §8.5, bl-269a) — the one
//! mechanism, consulted by both faces.
//!
//! A stop here is *"stop taking new work, finish what is running, drop the
//! engine"* — and it is almost entirely a subtraction, because dropping the
//! engine is already the whole of it. Every thread [`Engine::boot`] spawns
//! owns the §7.2 shutdown shape (stop flag, park loop, a [`Drop`] that unparks
//! and joins), so an engine dropped at the end of a consumer pass is an engine
//! that refused the next deposit and completed the last one; and every child
//! yog holds a pipe on is behind a [`Stream`](crate::cli_outbound::Stream)
//! whose own drop posts a polite `SIGTERM`, waits a grace, then escalates.
//! Nothing in this module drains, orchestrates or waits on its own account.
//!
//! **What was missing was only the catch.** `yog serve` parked forever with
//! the default `SIGTERM` disposition, so the signal killed the process where
//! it stood: no `Drop` ran, the `Engine` was never dropped, and the polite
//! `SIGTERM` above was never posted. So the fix is a disposition
//! ([`cli_outbound::sys::term_disposition`](crate::cli_outbound)), a flag, and
//! a loop that ends.
//!
//! **The bound is systemd's, not ours.** A consumer pass may be inside a `bl
//! close`; a stop lets it finish and adds no deadline of its own, because the
//! unit already states one (`TimeoutStopSec=30s`, until now unused) and the
//! kernel's `SIGKILL` at the end of it is the only bound that cannot be
//! out-waited. A second timeout in yog would be a worse copy of it.
//!
//! **The substrate settles itself.** A turn in flight is a *detached* `litany`
//! driver in its own process group (§8.1) — deliberately not yog's to kill,
//! and unaffected by yog's exit either way. Under the unit it takes the
//! cgroup's own `SIGTERM` beside yog's, and the pinned litany catches that and
//! deposits its branch's result message with a `stopped` epitaph on the way
//! out (its ARCH §2.9). So yog neither drains nor signals a turn: it stops
//! being in the way of one.
//!
//! **Both faces, one mechanism** (VISION V5.4). They differ only in the loop
//! that consults it, exactly as they already differ in a repaint hook: the
//! windowless face's loop is [`Engine::park_until_stopped`] below, and the
//! window's is eframe's, which asks [`requested`] each frame and closes its
//! viewport — the same close a human's click makes, so the engine drops down
//! the same path either way. Neither face adds a verb, a flag or a config
//! key: the signal is the signal.
use Engine;
use cratesys;
use ;
use Duration;
/// How often a parked face looks at the flag. A latency knob and not a
/// correctness one (the consumer's own poll is spelled the same way): the flag
/// is level, so a look that is late costs shutdown latency and nothing else.
/// A handler may not `unpark` — `Thread::unpark` is not async-signal-safe — so
/// looking is what there is.
const LOOK: Duration = from_millis;
/// Point `SIGTERM` at the flag instead of at process death. Called once per
/// face at the process edge (`main.rs`), above eframe and beside the other
/// edge-bound arms — and *only* by the two faces that boot an engine, never by
/// `yog env`/`exec`/`tool-control`, which have no engine to drop and for which
/// dying on the spot is the right answer.
/// Has a stop been asked for? Level, not an edge: a face may look as often or
/// as rarely as its own loop allows.
/// Park until `flag` is raised. Injected rather than read from the static so
/// the loop is driven with a flag a test owns; the one production caller hands
/// it the process's own.