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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! The process's ONE `notify` instance, fanned out per watched root (§7.1).
//!
//! **An inotify instance is not a private resource.**
//! `fs.inotify.max_user_instances` (128 by default) is a *per-user* kernel
//! budget shared with every other process the operator runs. yog used to open
//! one instance per watched root — five enumeration roots plus one per
//! workspace — and a failed arm is silent by design:
//! [`WatchSet::reconcile`](crate::watch::WatchSet::reconcile) skips the key and
//! retries. So the budget running out did not surface as an error, it surfaced
//! as watches that were simply never armed, and the §7.2 drift instrumentation
//! reading `false` from
//! [`WatchSet::watches`](crate::watch::WatchSet::watches). Four concurrent test
//! binaries, or a yog with a hundred workspaces, is ordinary load, not an
//! extreme (bl-908c).
//!
//! One instance can hold `fs.inotify.max_user_watches` (65536) descriptors, so
//! sharing it moves the constraint from the budget that binds at ~20 roots to
//! the one that binds at tens of thousands of directories. The hub owns that
//! instance for the whole process and delivers each raw event to every
//! registered root that contains it; the per-root [`RootKind`](super::RootKind)
//! filtering is untouched and still happens in [`Watcher::tick`](super::Watcher).
//!
//! **Two consequences of sharing, both deliberate:**
//!
//! - A backend *error* (a watch it could not arm mid-tree) has no reliable root
//! attribution once the instances are one, so it is delivered to every live
//! root. That is a superset of the truth — extra re-derivation, never a
//! missed one — and it is what [`ChangeKind::Desynced`](super::ChangeKind)
//! already means: "re-read this root".
//! - `notify`'s inotify backend removes every descriptor *underneath* the path
//! it unwatches, so dropping a watcher on an enumeration root would deafen a
//! workspace watcher nested inside it. [`disarm`] re-arms every overlapping
//! live root instead of leaving a deaf watcher (§7.3).
//!
//! **Why the locks live here and not in `state.rs`** (Bootstrap rule 7's second
//! sanctioned carve-out, declared in `rules/locks-outside-state.yml`): both are
//! `OnceLock` process singletons that are never dropped and never handed out, so
//! they are not the cross-thread handoff `state.rs` inventories — and folding
//! them in costs the chokepoint its 100 % floor for the same llvm-cov reason
//! `git_tree::probe_cache` records: adding types there shifts the file's byte
//! offsets and llvm-cov mis-attributes phantom uncovered regions onto its `impl`
//! headers and type aliases (measured: 3 lines, on a file that is otherwise 100 %).
use WatchError;
use ;
use ;
use ;
use ;
/// One fan-out subscriber: a canonical watched root and the channel the
/// [`Watcher`](super::Watcher) over it drains.
type Slot = ;
/// The registry the backend's event thread delivers through — named directly by
/// the callback, which therefore captures nothing.
static SLOTS: = new;
/// The process's one backend, or `None` if it could not be created at all (the
/// budget was gone before the first arm) — every caller then degrades exactly as
/// it did on a per-root arm failure.
static BACKEND: = new;
/// The registry, locked poison-immune (the one-line recovery discipline
/// `state::lock_watchset` records: a split reads as uncovered under
/// `ignore-panics`).
/// The backend, locked. Never taken while [`slots`] is held: the backend's own
/// event loop runs the fan-out callback, so a thread holding the registry across
/// a `watch()` call would deadlock against its own notification.
/// Fan one raw backend message out to every root it concerns.
/// The message `root` should see, or `None` when this one is not its business.
///
/// A rescan flag (inotify `IN_Q_OVERFLOW`) and an error are the *instance's*
/// losses, not one root's, so both reach every root. An ordinary event is
/// narrowed to the paths under `root` — which keeps a rename's `(from, to)`
/// pair intact whenever both ends are inside it.
pub
/// Arm `root` (already canonicalized) on the shared instance and return the
/// channel its events arrive on. The watch is taken before the slot is
/// registered, so a failure leaves no subscriber behind.
pub
/// Retire one subscriber on `root`, unwatching it once nothing else wants it —
/// and re-arming every live root the unwatch took down as collateral (see the
/// module doc).
pub