lwkt_rele() never wakes the td_refs drain pollers (tdreap/lwpwait2) β teardown latency and needless scheduler churn only
Summary
lwkt_rele decrements td_refs without issuing wakeup(td), while both drain sites (lwkt_exit's tsleep(td,0,'tdreap',1) and lwp_wait's tsleep(td,0,'lwpwait2',1)) poll on 1-tick tsleeps β every thread teardown behind a held reference is delayed by up to a full tick after the last release with the exiting/reaping thread waking every tick meanwhile. Correctness preserved; impact bounded to sub-tick teardown latency and scheduler load. Wakeup-on-drain fix proposed.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5840 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 1.6 KB | β raw | |
| verdict.json | file | 2.0 KB | view raw |
DF-5840 β lwkt_rele() never wakes the td_refs pollers (tdreap / lwpwait2)
Info-grade hardening finding; verification SKIPPED per the audit contract (Low/Info hardening findings with no memory-safety consequence).
What
lwkt_rele() (sys/kern/lwkt_thread.c:502-507) decrements td_refs but never issues wakeup(td), while both drain sites POLL instead of being woken:
- lwkt_exit() :1708-1711
tsleep(td, 0, "tdreap", 1)β 1-tick poll - lwp_wait() kern_exit.c:857-859
tsleep(td, 0, "lwpwait2", 1)β poll, returns 0 to force the caller to retry lwp_wait()
Every thread teardown behind a held reference is therefore delayed by up to
one full tick after the last lwkt_rele(), and the exiting/reaping thread
burns a wakeup-per-tick cycle in the meantime. Correctness is preserved
(the polls do drain); this is latency + needless scheduler load only β
under a ref-heavy workload (e.g. ps -auxH storms holding td refs from
kern_proc.c:1817 concurrent with kthread churn) exit latency inflates by
up to 10 ms per poll iteration at hz=100.
Recommended fix
wake the pollers at drain: in lwkt_rele(), after the decrement, if td_refs == 0 issue wakeup(td) (the wchan both pollers sleep on):
void
lwkt_rele(thread_t td)
{
KKASSERT(td->td_refs > 0);
atomic_add_int(&td->td_refs, -1);
+ if (td->td_refs == 0)
+ wakeup(td);
}
(wakeup on an empty channel is harmless; both pollers sleep on td itself as the ident.)
Verification
Not executed: no security impact to demonstrate (no corruption, no leak, no achievable DoS beyond sub-tick teardown latency). status: untested.
Fix verification
not_testableNot applicable: verification skipped for this Info hardening finding; the one-line fix is low-risk and mechanically obvious.
[]
Confirmed kernel references
Detail
Evidence (decisive lines)
['sys/kern/lwkt_thread.c:502-507 (rele without wakeup)', 'sys/kern/lwkt_thread.c:1708-1711 (tdreap 1-tick poll)', 'sys/kern/kern_exit.c:857-859 (lwpwait2 1-tick poll)']
PoC changes
no PoC built (Info hardening; skipped per contract)
Verified recommended fix
lwkt_rele(): issue wakeup(td) when the decrement reaches zero (both poll sites sleep on td as their ident).
Verdict
Info-grade hardening finding, verification intentionally skipped per the audit contract (no memory-safety consequence): lwkt_rele() (lwkt_thread.c:502-507) never wakes the td_refs drain pollers - lwkt_exit()'s tsleep(td,0,"tdreap",1) at :1708-1711 and lwp_wait()'s tsleep(td,0,"lwpwait2",1) at kern_exit.c:857-859 - so every teardown behind a held reference is delayed by up to one tick after the last release and the poller burns scheduler cycles meanwhile. Correctness is preserved (the polls drain the refcount); impact is bounded teardown latency and scheduler load only. One-line fix: wakeup(td) on the 1->0 transition in lwkt_rele().
No comments yet.