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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
//! Reader-reported open-descriptor accounting for [`catalog::READER_FDS_OPEN`]
//! (issue #1707, AI7 of epic #1686).
//!
//! # Why the readers report it, and NOT `/proc`
//!
//! `cqlite.proc.fds` already samples `/proc/self/fd` every ~2s (issue #2419) — the
//! whole process, Linux only, sockets and WAL included. This gauge answers the
//! narrower question an operator actually needs when a read path is close to
//! `EMFILE`: how many descriptors are the SSTable READERS holding right now? The
//! readers know that exactly, at the moment it changes, on every platform — so it is
//! reported, never sampled and never inferred from a byte pattern or a directory
//! listing.
//!
//! # It counts DESCRIPTORS, so several honest zeros exist
//!
//! An mmap-backed source holds a MAPPING, not a descriptor (the fd is closed right
//! after `mmap`), and an `Arc` clone of an existing handle is not a new descriptor.
//! Both contribute NOTHING here rather than a plausible-looking number: the #2314
//! authoritative-data rule forbids inventing a value nobody measured, and an
//! inflated fd count would send an operator hunting a limit they are nowhere near.
//!
//! # One atomic op per change, and the increment cannot be skipped
//!
//! [`OpenFdGauge::minted`] is the only PRODUCTION constructor and it increments;
//! `Drop` decrements. The type's single field is private, so it cannot be built by a
//! struct literal that forgets the increment (the same structural guarantee
//! `read_path_probe::BlockingScanTaskGuard` uses), and it is deliberately NOT
//! `Clone`/`Copy` — cloning it would decrement twice for one descriptor.
//!
//! Ordering is `AcqRel` on both sides so a reader thread that observes the
//! decrement also observes everything the closing thread did before releasing the
//! handle; the gauge emission then reports the value the same atomic op returned,
//! never a separate `load` (a load-then-record pair can report a value that was
//! never current under concurrency).
//!
//! # What that does and does NOT promise (issue #1707)
//!
//! It promises that every value REPORTED was the true level at some instant — no
//! reading is fabricated, and none is a stale re-read of a counter that has since
//! moved. It does NOT promise the last reported value is the CURRENT level: the
//! atomic op and the gauge emission are two steps, so two threads transitioning at
//! once (A `fetch_add`→1, B `fetch_sub`→0) can emit in the opposite order to the one
//! they transitioned in, leaving `1` as the last reported value until the NEXT
//! transition corrects it. The window is one emission and it is self-healing, which
//! is why there is deliberately no `Mutex` here: serialising every open and close of
//! a descriptor to tighten a gauge would be paying real contention on the read path
//! for a reading that is already eventually right, and the pre-existing
//! `SSTABLES_OPEN` counter has the byte-for-byte identical shape — one lock here
//! would leave two patterns for one problem. Read the gauge as a level with a
//! transition-latency of one event, not as a serialised ledger.
use ;
use cratecatalog;
/// Descriptors the SSTable readers currently hold open, process-wide.
///
/// `i64` because the gauge is `i64`, and because a signed type makes an unpaired
/// decrement show up as a NEGATIVE reading — visibly wrong — instead of wrapping to
/// a huge positive one that reads like genuine fd pressure.
static READER_FDS_OPEN: AtomicI64 = new;
/// One open file descriptor's presence in [`catalog::READER_FDS_OPEN`].
///
/// Stored BESIDE the handle it accounts for, so its lifetime is the descriptor's:
/// every close path — a clean drop, an early return, an unwind — decrements exactly
/// once, with no `close`-site bookkeeping to forget.
///
/// # Declare it AFTER the handle — the field order is load-bearing
///
/// Rust drops struct (and enum-variant) fields in DECLARATION order, so a guard
/// declared BEFORE the handle decrements while the process still holds the
/// descriptor: the gauge briefly reports FEWER open fds than are really open, which
/// is the wrong direction for a metric whose whole purpose is spotting `EMFILE`
/// pressure. Every holder in this module tree therefore declares
/// `field-holding-the-handle` first and the `OpenFdGauge` last, with a note at the
/// site so a later tidy-up does not silently reorder them.
///
/// The single field is PRIVATE, so this cannot be built by a struct literal that
/// forgets the increment (the same structural guarantee
/// `read_path_probe::BlockingScanTaskGuard` uses), and the type is deliberately not
/// `Clone`/`Copy` — cloning it would decrement twice for one descriptor.
pub
/// Emit the gauge. No-op (and zero-cost) when the `observability` feature is off or
/// no meter provider is installed.
/// The current level, for tests and for a caller that wants the reading without
/// waiting for the next change.
pub