fix(wake): #927 enqueue TOCTOU — move stale-tmp cleanup off the hot enqueue path
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
cmd_enqueue called _wake_init_dir() (which reaped EVERY .wake.tmp.*
unconditionally) BEFORE taking the enqueue lock. So a 2nd enqueue's PRE-LOCK
cleanup deleted the LIVE in-flight tmp of a 1st enqueue that held the lock
through its atomic write -> spurious "durable pending write FAILED" abort of a
valid enqueue. Reachable under live co-feed (detector + reconciler concurrently
enqueue).
Fix (scalpel, Mos's lean = off the hot path):
- _wake_init_dir no longer reaps tmps; it only ensures the layout. Nothing on
the enqueue/consume/cursors/ack hot paths can clobber a concurrent live write.
- _wake_clean_stale_tmp is AGE-SCOPED (-mmin +${WAKE_TMP_STALE_MIN:-5}) so it
can only remove demonstrably-orphaned crash-left tmps, never a live (ms-old)
in-flight write — safe even if it ever overlaps a concurrent enqueue.
- Reaping runs as an explicit MAINTENANCE action at store.sh init (daemon-start)
and the detector poll tick, bounding accumulation once-per-pass, not raced
per-enqueue.
#908 seq-integrity is unchanged (single store-side allocator, atomic
allocate+enqueue under flock, arrow-1 no-burn, anti-swallow fail-loud).
reconcile.sh is unchanged: an aborted enqueue advances neither the seen-ledger
nor observed_seq, so the source is re-enumerated next reconcile cycle (no
obligation loss).
Tests: new deterministic RED->GREEN harness test-wake-store-enqueue-race.sh
(PATH-shadowed mv/flock freeze enqueue A at its rename while B runs its pre-lock
cleanup — RED pre-fix: A aborts; GREEN post-fix: both succeed, distinct gapless
seqs {1,2}, zero spurious aborts). T5 updated to assert the hot path no longer
reaps tmps and that the maintenance path reaps a demonstrably-orphaned tmp. All
store-ack groups incl T10 stay green. manifest 0.6.4 -> 0.6.5.
Closes #927
Part of #892
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,11 @@ cmd_init() {
|
||||
echo "store.sh: failed to init $STATE_DIR" >&2
|
||||
exit 1
|
||||
}
|
||||
# Maintenance/daemon-start stale-tmp reap (#927). Cleanup is OFF the per-enqueue
|
||||
# hot path; `init` is a natural once-per-start maintenance point. It is
|
||||
# age-scoped, so even if an operator runs `init` while an enqueue is in flight
|
||||
# it can only reap demonstrably-orphaned (crash-left) tmps, never a live write.
|
||||
_wake_clean_stale_tmp "$STATE_DIR"
|
||||
echo "$STATE_DIR"
|
||||
}
|
||||
|
||||
@@ -163,6 +168,11 @@ cmd_enqueue() {
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Ensure the layout exists. NB (#927): _wake_init_dir NO LONGER reaps stale
|
||||
# tmps — that would race a concurrent enqueue's live in-flight write (deleting
|
||||
# its tmp mid-rename -> spurious "durable pending write FAILED"). Stale-tmp
|
||||
# reaping is a maintenance action (store.sh init / detector tick), never on this
|
||||
# hot path.
|
||||
_wake_init_dir "$STATE_DIR"
|
||||
|
||||
# --- serialize the whole allocate+write transaction (#908) ----------------
|
||||
|
||||
Reference in New Issue
Block a user