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
//! 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).
//!
//! **Both locks live in [`state`](crate::state)** (AGENTS.md rule 7):
//! [`hub_slots`] and [`hub_backend`] there are the registry and the backend.
use WatchError;
use crate;
use ;
use ;
use MutexGuard;
use ;
/// The backend, locked — `None` if it could not be created at all (the budget
/// was gone before the first arm), and every caller then degrades exactly as it
/// did on a per-root arm failure. 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.
/// The one backend, whose callback names the registry directly and therefore
/// captures nothing.
/// 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