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
314
315
316
317
318
319
320
321
322
323
/*!
# `setback`: setjmp/longjmp failure recovery, confined to C
[`protect`] runs a closure and returns `Ok(value)` on normal completion, or
`Err(RecoveryError)` if a `longjmp` - triggered by a stack-overflow fault
handler, an out-of-memory handler, or explicit user code via [`recover`] -
abandons the closure's stack. Everything on the abandoned stack is leaked: no
`Drop` runs. See [`protect`] for the full safety contract.
## How it works
All `setjmp`/`longjmp` lives in a tiny C file (`setback.c`): rustc does not support
`setjmp`/`longjmp`, so calling `setjmp` from Rust risks miscompilation. Rust hands
C a data pointer and an `extern "C"` trampoline, C arms the mark and calls the
trampoline, which runs the closure. A `longjmp` resets the stack pointer to
that `setjmp`, jumping over every live Rust frame above it - the trampoline, the
closure, and its whole call tree - and abandons them where they sit. The jump
stops at the C frame, and [`protect`] returns `Err(RecoveryError)`.
An uncaught panic crossing the `extern "C"` trampoline aborts (Rust 1.81+)
rather than entering C.
## One global registry, keyed by thread id
The crate owns a single `static` intrusive doubly-linked list of active marks.
Each [`protect`] call links one node, tagged with the caller's [`ThreadId`], and
unlinks it on exit. One shared fault handler, given the *faulting* thread's id,
calls [`recover`] to find that thread's innermost active mark and jump into it.
The link/unlink runs inside a [`critical_section`], the protected closure runs
outside it. You supply the [`critical-section`] impl in the final binary.
[`critical-section`]: https://docs.rs/critical-section/latest/critical_section/
*/
compile_error!;
use UnsafeCell;
use Infallible;
use Error;
use c_void;
use ;
use UnwindSafe;
use ptr;
/// Identifier the caller uses to tag a `protect` scope and that the fault
/// handler uses to find it again. Cast your RTOS task handle / index to `usize`.
pub type ThreadId = usize;
/// Wrap a capture (or a whole closure) to assert it is unwind-safe if needed,
/// satisfying the [`UnwindSafe`] bound on [`protect`]. Safe in itself, you
/// should still fulfill the safety contract of [`protect`] when the closure runs.
pub use AssertUnwindSafe;
/// Returned by [`protect`] when the closure's stack was abandoned by a `longjmp`.
/// Returned by [`recover`] when the given `tid` has no active [`protect`] scope.
;
unsafe extern "C"
const SETBACK_OK: i32 = 0;
/// Bytes of stack that [`protect`] reserves below the recovery mark before it
/// runs the closure - the gap a fault handler may rely on when choosing where
/// to run [`recover`]. See the "Recovery-stack guarantee" on [`protect`].
//
// Must stay equal to `SETBACK_RECOVERY_GAP_BYTES` in `setback.c`.
pub const RECOVERY_GAP_BYTES: usize = 64;
/// Backing storage for one C `jmp_buf`. 512 bytes / 16-byte alignment covers
/// every mainstream target. The constructor asserts it.
// SAFETY: every access runs inside critical_section::with, which the provided
// impl makes mutually exclusive across all threads and cores.
unsafe
static REGISTRY: Registry = Registry ;
/// Run `f` under recovery protection, tagging this scope with `tid`.
///
/// Returns `Ok(value)` on normal completion, or `Err(RecoveryError)` if
/// [`recover`] (from the fault/OOM handler) jumped into this scope. On the
/// `Err` path everything `f` had on the stack is leaked: no destructors run.
/// Nesting is supported (the handler resolves to the innermost scope for `tid`).
/// Note that nesting different `tid`s will lead to UB.
///
/// ## The [`UnwindSafe`] bound
///
/// `protect` requires `F: UnwindSafe` for the reason `std::panic::catch_unwind`
/// does: a closure abandoned mid-mutation can leave a value torn, so the bound
/// makes the usual offenders (`&mut T` captures, `Cell`/`RefCell`/`Mutex`) fail
/// at the call site instead of passing silently. It is advisory -
/// [`AssertUnwindSafe`] satisfies it unconditionally and safely. The obligations
/// the type system cannot express are in `# Safety` below, which is why
/// `protect` is `unsafe`.
///
/// ## Recovery-stack guarantee
///
/// Before calling `f`, `protect` reserves at least [`RECOVERY_GAP_BYTES`] of
/// stack between the closure and the recovery mark (the `setjmp` point) and
/// holds it reserved for the whole run, so `f` never touches it. This gives a
/// fault handler somewhere to stand: to turn a fault into an `Err`, the handler
/// resumes the faulting thread and calls [`recover`], which must not overwrite
/// the mark, the saved `jmp_buf`, or any frame at or before the `protect` call.
/// Those all sit at or before the mark, and the reserved gap guarantees room
/// below it - so a handler may land `recover` at the bottom of the thread's
/// stack and run entirely on abandoned frames.
///
/// Gap isn't designed to always be a place to run the handler, but it gives you
/// a guarantee the you can go off [`RECOVERY_GAP_BYTES`] bytes before the stack
/// bottom.
///
/// # Safety
///
/// Recovery rewinds the stack pointer and runs no destructors: every frame `f`
/// pushed is leaked in place and its storage is reused by later calls. The
/// caller must ensure nothing depends on those frames living on, or on their
/// `Drop` running. This is non-exhaustive - among the things it breaks:
///
/// - `Pin`'s drop guarantee for stack-pinned `!Unpin` values
/// (`core::pin::pin!`, an on-stack address-sensitive future, an intrusive
/// node): the storage is invalidated and reused with no `Drop`. (`Pin<Box<T>>`
/// is safe - heap storage is only leaked.)
/// - Raw pointers into the frames dangle after `Err`: fine to hold, UB to
/// dereference.
/// - References into the frames dangle too, and a reference can be UB just by
/// staying live across recovery (using it retags it), not only when read.
/// - Scope-based APIs (such as `thread::scope`) are bypassed.
/// - `Drop`-based invariants (lock guards, `RAII cleanup) do not run.
/// - Interior-mutable state shared outward can be left torn if `f` was
/// abandoned mid-mutation.
///
/// ...and anything else that assumed the stack above the mark stayed valid.
pub unsafe
unsafe extern "C"
/// From the shared fault/OOM handler: recover the thread identified by `tid` by
/// jumping into its innermost active [`protect`] scope, reporting `cause`.
///
/// Diverges on success: the matching [`protect`] returns
/// `Err(RecoveryError { cause })`. Returns `Err(RecoveryFailure)` if `tid` has no
/// active scope, so the caller can halt or escalate.
///
/// # Safety
/// - `tid` must identify the thread on whose stack the matching `protect` is
/// still live.
/// - Must be called from the same thread as `tid`, not from the other thread,
/// context, or the fault handler.
/// - All leak / `protect` `# Safety` obligations apply to everything between
/// the fault point and the mark.
pub unsafe
unsafe
unsafe
unsafe