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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Windows backend for `lifecycle::supervisor`: a single process-wide Job
//! Object, created lazily *only* in the process that spawns generation 1
//! (`xr dev`/`xr restart` - see `register`'s own doc comment for why later
//! generations must *not* each create their own), configured with
//! `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` - the OS kills every process still
//! in the job the moment the job object's last handle closes. `xr dev`
//! itself holds that one handle (created lazily below, never duplicated
//! into any child, never explicitly closed) for its entire lifetime, so
//! that "last handle closes" moment is exactly "`xr dev` itself exits, for
//! any reason" - a crash, `taskkill /F`, a closed terminal window - not
//! just the graceful paths (Ctrl+C, the admin channel's `STOP`) this
//! codebase already handles elsewhere.
//!
//! `xr dev`'s own process does *not* need to be explicitly assigned to
//! this job - `KILL_ON_JOB_CLOSE` triggers on the job handle's own
//! lifetime, not on job membership. Windows closes every handle a process
//! holds when that process exits, regardless of how it exits; as long as
//! nothing else ever holds a second handle to this job (the default -
//! handles aren't inherited by spawned children unless explicitly marked),
//! `xr dev` exiting is the only thing that can ever release it.
//!
//! Generation 1, once assigned to this job (`register`, called with `xr
//! dev` as the caller), automatically carries that membership forward:
//! Windows adds any child a job member spawns to the *same* job with no
//! further API calls, all the way down the handoff chain (generation 1
//! spawning generation 2, generation 2 spawning generation 3, ...) - this
//! is original Job Object behavior, not a Windows-8-era nested-jobs
//! feature. So only the very first hop needs `register` called at all;
//! every later, server-to-server hop inherits automatically. Calling
//! `register` again on a later hop (this module's own behavior before a
//! real, reproduced bug got fixed here) doesn't add redundant protection -
//! it *creates a second, unrelated job*, owned solely by whichever
//! generation performed that spawn, and reassigns the new replacement into
//! it instead. That second job's `KILL_ON_JOB_CLOSE` then fires the moment
//! its own creator exits - including that generation's own *normal,
//! expected* exit once its handoff succeeds - killing the brand-new
//! replacement as collateral damage of the very success that was supposed
//! to let its predecessor retire. See `handoff::
//! spawn_replacement_and_wait_for_ready`'s own doc comment for the
//! `register_with_supervisor` parameter this depends on to avoid it, and
//! `docs/GOTCHAS.md` for how this was actually diagnosed.
use OnceLock;
use HANDLE;
use ;
/// A `HANDLE` is an opaque reference into the kernel's object table (a
/// `*mut c_void` in this `windows-sys` version, but never dereferenced as
/// a pointer to memory this process manages) - safe to share across
/// threads. This wrapper exists only so `OnceLock` has a named, `Send +
/// Sync` type to hold.
;
// SAFETY: see the doc comment above.
unsafe
// SAFETY: see the doc comment above.
unsafe
static JOB: = new;
/// Best-effort: assigns `child` to the process-wide supervision job (see
/// this module's own doc comment), creating the job on first use. Logs a
/// warning and does nothing further on any failure - never treated as
/// fatal to the handoff that's spawning `child` in the first place.
pub