From ead1f5b32487e988cca4bccc05cf2076c8060a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Sun, 14 Jun 2026 11:37:48 -0400 Subject: [PATCH] ke: fix livelock in turnstile --- src/kern/turnstile.zig | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/kern/turnstile.zig b/src/kern/turnstile.zig index 1dd47e3..ca7045e 100644 --- a/src/kern/turnstile.zig +++ b/src/kern/turnstile.zig @@ -135,9 +135,11 @@ fn reboost(ts: *Turnstile, to: *ke.Thread) void { /// successive owner until one is already at least as high. /// /// Locking: -/// - `root` is held by the caller throughout and never dropped here. -/// - Any other chain bucket is taken with trylock, failure means restart. At -/// most one such bucket is held at a time. +/// - `root` is held by the caller on entry and is held again on return. +/// - If another chain bucket cannot be trylocked, we drop the current thread +/// lock and `root`, then reacquire `root` and restart from `curtd`. +/// - Any other chain bucket is taken with trylock. At most one such bucket is +/// held at a time. fn propagate(curtd: *ke.Thread, root: *Chain) void { var donate = curtd.priority; var thread = curtd; @@ -150,10 +152,26 @@ fn propagate(curtd: *ke.Thread, root: *Chain) void { const bucket = &chains[hash_obj(obj)]; // Lock `bucket` and confirm `thread` didn't move before we got it. - // If either fails, restart below. + // If the trylock fails, drop root and restart from curtd. + // If validation fails, restart while still holding root. const restart = blk: { if (bucket != root) { - if (!bucket.lock.try_acquire_no_ipl()) break :blk true; + if (!bucket.lock.try_acquire_no_ipl()) { + std.debug.assert(held == null); + + // Drop everything and try again. This avoids a livelock + // between two PI walks holding different roots and trying to + // acquire each other. + thread.lock.release_no_ipl(); + root.lock.release_no_ipl(); + + root.lock.acquire_no_ipl(); + curtd.lock.acquire_no_ipl(); + + thread = curtd; + donate = curtd.priority; + continue; + } held = bucket; } break :blk thread.waiting_on != obj;