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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//! Tiny jemalloc-linked target for the probe's cross-process
//! closed-loop test (see `tests/jemalloc_probe_tests.rs`).
//!
//! # Modes
//!
//! - **Default**: reads `<BYTES>` from argv, allocates on the main
//! thread, writes the pid-scoped ready marker via
//! [`ktstr::worker_ready::worker_ready_marker_path`], then parks
//! forever. Single-threaded so `tid == pid` and the test can
//! match on `threads[N].tid` without a TID handshake; a
//! `/proc/self/task` self-check rejects any silent extra thread
//! (e.g. an allocator background thread or a runtime pulled in
//! by a new dep).
//! - **`--churn`**: after the main allocation and ready-marker,
//! loops spawn+join on helper threads to exercise the probe's
//! ESRCH handling in
//! `jemalloc_probe_survives_thread_churn`. Relaxes the
//! single-thread self-check — the `tid == pid` invariant is
//! therefore not guaranteed in this mode.
//!
//! Links `tikv_jemallocator` so the probe's DWARF-backed lookup
//! finds `tsd_s.thread_allocated`. Minimum allocation is 16 KiB (to
//! route through jemalloc's huge/large path and skip tcache
//! deferred updates); the test passes 16 MiB. Keep the binary
//! minimal — it travels in the guest initramfs next to the probe.
//!
//! # Exit codes
//!
//! Process termination is always by external signal; any non-zero
//! exit is a setup failure. Negative `status.code()` (via
//! `ExitStatus::signal()` on unix) means the worker was killed by a
//! signal — the expected termination path in the VM-based
//! `jemalloc_probe_tests` (`PayloadHandle::kill` → SIGKILL). The
//! table below enumerates every observable exit status the worker
//! can produce so a failing test can grep the full set in one pass:
//!
//! | Code | Condition |
//! |------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
//! | `0` | Normal `exit(0)`. Unreachable under correct execution — the default mode parks forever (`sleep(3600s)` loop) and `--churn` spins `loop { spawn+join }` with no break. Listed for grep completeness: a `0` in practice means a refactor dropped the terminal-loop black-boxing and the optimizer folded the loop away. |
//! | `2` | `bytes == 0` (caller bug — zero-size alloc would panic in `touch`). |
//! | `3` | Default-mode `/proc/self/task` self-check saw a thread count != 1. Typical cause: `MALLOC_CONF=background_thread:true` / `_RJEM_MALLOC_CONF=…` spawned a jemalloc helper during init. |
//! | `4` | Ready-marker write failed (path unwritable, e.g. parent directory missing). |
//! | `5` | Argv parse failed (missing `<BYTES>` or not a `usize`). |
//! | `6` | Default-mode `read_dir("/proc/self/task")` itself failed. Distinct from code 3 ("procfs readable but reported extra threads") — 6 points at a broken/unmounted procfs. |
//! | `101` | Rust panic. `panic!` / `debug_assert!` / indexing out-of-bounds routes through the default panic hook. Matches the `jemalloc_probe_tests.rs` ready-marker-wait legend so an operator seeing `101` locates it here without cross-referencing the panic hook behavior elsewhere. |
//!
//! Argv is hand-rolled (one flag, one positional). The tipping
//! point for switching to `clap-derive` is when the surface needs
//! structured help text (`--help`), multiple subcommand-like modes,
//! value validation beyond `str::parse`, or environment-variable
//! fallbacks — any ONE of those lands in under ten lines of
//! derive-macro annotations and would cost about the same in hand
//! code. Adding a single extra `--flag` or positional while the
//! surface still fits in ~10 lines of std code is not enough to
//! pull in the dependency.
static GLOBAL: Jemalloc = Jemalloc;
use Write;
use Duration;
// Share the ready-marker path format with the ktstr library
// (`src/worker_ready.rs`) via `#[path]` rather than `use ktstr::...`.
// Linking the ktstr library into this binary would pull in the
// library's early-dispatch ctor (`test_support::dispatch::ktstr_test_early_dispatch`,
// tagged `#[ctor::ctor]`) plus the rest of the crate, bloating the
// initramfs image and adding `.init_array` work that can stall the
// probe's cross-process timing. `#[path]` compiles the same source
// file into this bin crate directly — zero linker cost, same
// single-source-of-truth behavior.
use ;
/// Force jemalloc to observe the heap buffer by reading through it.
///
/// LLVM can in principle observe that a freshly-zeroed `Vec`'s heap
/// buffer is never read through the pointer and fold the allocation
/// away. Black-boxing a reference alone is not strong enough. Force
/// an actual heap read through `v[0]` plus black-box the raw pointer
/// and length so the allocation is provably materialized and
/// jemalloc's `thread_allocated` counter is updated before the probe
/// sees it.
///
/// Inlined at every tick of the churn and park loops so the optimizer
/// cannot move the free before the loop iteration.