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
//! Process-global liveness registry for cross-thread wakeups of per-thread
//! `MiniEventLoop`s.
//!
//! ## Root cause this module eradicates (BCE-20260814-TLS-DRIVER-UAF)
//!
//! A `MiniEventLoop` is `Box::into_raw`-leaked per thread (timers.rs
//! `BAO_RUNTIME_LOOP`), so a captured `*mut MiniEventLoop` stays valid
//! memory forever. Its `loop_` field, however, points at the C-owned uws
//! loop owned by bao_uloop's thread-local `BaoLoopState` — whose `Drop`
//! **frees that loop when the owning thread exits** (bao_uloop
//! `BaoLoopState::drop`). Cross-thread callers that held only the
//! `MiniEventLoop` pointer (the `bao-tls-driver` thread's
//! `tls_schedule_tasklet`, HTTPThread-side `resolve_tasklet` scheduling)
//! woke the uws loop via `enqueue_task_concurrent` under the assumption
//! "valid for the thread's lifetime" — false after the owning JS thread
//! exits: `us_wakeup_loop` on freed loop memory → SIGSEGV in `us_poll_fd`
//! (tls_sni_server_tests under load: ~50% crash rate). The existing
//! `is_null()` guards only covered "loop never captured", never
//! "captured, then thread exited" (TOCTOU).
//!
//! ## Fix: registry + lock handshake
//!
//! Every thread registers its `MiniEventLoop` → uws-loop pair here at
//! materialization; a thread-exit guard deregisters it. Cross-thread
//! enqueue checks membership **and performs the wakeup while holding the
//! registry lock**; deregistration takes the same lock **before** the
//! loop memory is freed. This makes the two races mutually exclusive:
//!
//! - An in-flight wakeup (lock held) completes before deregistration can
//! run, hence before the free.
//! - After deregistration returns, every future enqueue sees "not
//! registered" and never touches the loop.
//!
//! Teardown ordering (why the free really happens after deregister):
//! Rust runs a thread's TLS destructors in reverse initialization order.
//! `with_event_loop` materializes bao_uloop's `BAO_LOOP` first (inside
//! `MiniEventLoop::init` → `UwsLoop::get`) and registers here second, so
//! this module's guard drops (deregisters) *before* `BAO_LOOP`'s `Drop`
//! frees the uws loop. Production code never frees a thread's uws loop
//! mid-thread (`us_loop_free` is test-only), so no stale re-registration
//! window exists. The leaked `MiniEventLoop` box's address is never
//! reused, so the addr key has no ABA.
//!
//! Keyed by address (not a generation counter) because the MiniEventLoop
//! allocation is never freed — a stale entry can only mean "thread still
//! live", and a missing entry can only mean "thread exited" (or "never
//! registered"). Both are decided atomically under the lock, which is the
//! property a bare generation counter cannot provide (check-then-wakeup
//! would still race the free).
use HashMap;
use Mutex;
use Loop as UwsLoop;
use crateAnyTaskWithExtraContext;
use crateMiniEventLoop;
/// mini addr → its C-owned uws loop addr. Membership means "the owning
/// thread is still running and its uws loop is live". Both sides are
/// stored as `usize` (addresses as registry keys); raw pointers would
/// make the `Mutex` `!Sync`. `Option` because `HashMap::new` is not a
/// const fn — the map materializes on first registration.
static LIVE_MINI_LOOPS: = new;
/// Thread-exit deregistration guard. Materialized on the owning thread at
/// registration; `Drop` runs at thread exit, *before* bao_uloop's
/// `BAO_LOOP` destructor (TLS destructors run in reverse init order —
/// see module docs).
;
thread_local!
/// Register the current thread's `MiniEventLoop` as a live cross-thread
/// wakeup target. Called once, right after materialization (timers.rs
/// `with_event_loop`), on the owning thread only.
/// Cross-thread enqueue of a concurrent task onto a (possibly dead)
/// thread's `MiniEventLoop`, waking its uws loop.
///
/// Returns `false` when the owning thread has exited (or the loop was
/// never registered): nothing was pushed and no wakeup was attempted —
/// the caller must handle it exactly like the "loop not captured" case.
///
/// `mini` must be a pointer previously handed out by the owning thread's
/// `with_event_loop` (leaked allocation; never freed, so passing a stale
/// pointer after thread exit is defined behavior — it only reads fields
/// frozen at init and pushes into a queue nobody drains).