You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Observed during the #17 idle-preemption work (PR #28's verification notes) and flagged there as worth its own issue; filing it so it does not stay buried in a merged PR body.
Observation, as measured
testAnAlreadyCompleteSlotIsAwaitedWithoutParking.phpt failed once in four full-suite runs on PHP 8.4.19 (-d ffi.enable=1 -d opcache.jit=off).
The failure was on the cooperative path — no preemption involved.
Why this shape of race is plausible
The test promises that a slot which is already settled is awaited without parking. That promise has a window: the waiter must observe "already complete" through the same path (SlotTable::refresh() / local view) that a fresh settlement travels through. Under suite load, the wake event and the settle can land close enough together that the await neither finds the slot locally complete nor receives the level-triggered poke it would have parked on — or the test's own observable ("did it park?") reads a scheduler state that is one tick stale. Since #29/#31 the slot's local view is also claim-counted and forgettable, which adds one more ordering to hold (adopt-then-settle is already pinned by #31's adopt() fix — this may be a sibling of that bug on the open-then-await path).
Constraints on the fix
Not a retry and not a sleep: AGENTS.md — bound everything, wait on the primitive, and a Coroutine::yield() busy-loop starves the very timers under test.
The fix should make the "already complete ⇒ no park" claim true by construction (settle-before-publish ordering, or the test asserting the real invariant it means: no kernel wait happened, e.g. by wakeup count as testTheWakePipeIsDrainedSoThePollerDoesNotSpin.phpt does), rather than widening the window until CI stops noticing.
Reproduction guidance
Run the full suite in a loop (for i in $(seq 20); do ... vendor/bin/phpunit; done) rather than the single test; the 1-in-4 figure was under full-suite conditions only. A run under --repeat of just this file has not reproduced it.
Observed during the #17 idle-preemption work (PR #28's verification notes) and flagged there as worth its own issue; filing it so it does not stay buried in a merged PR body.
Observation, as measured
testAnAlreadyCompleteSlotIsAwaitedWithoutParking.phptfailed once in four full-suite runs on PHP 8.4.19 (-d ffi.enable=1 -d opcache.jit=off).Why this shape of race is plausible
The test promises that a slot which is already settled is awaited without parking. That promise has a window: the waiter must observe "already complete" through the same path (
SlotTable::refresh()/ local view) that a fresh settlement travels through. Under suite load, the wake event and the settle can land close enough together that the await neither finds the slot locally complete nor receives the level-triggered poke it would have parked on — or the test's own observable ("did it park?") reads a scheduler state that is one tick stale. Since #29/#31 the slot's local view is also claim-counted and forgettable, which adds one more ordering to hold (adopt-then-settle is already pinned by #31'sadopt()fix — this may be a sibling of that bug on the open-then-await path).Constraints on the fix
Coroutine::yield()busy-loop starves the very timers under test.testTheWakePipeIsDrainedSoThePollerDoesNotSpin.phptdoes), rather than widening the window until CI stops noticing.Reproduction guidance
Run the full suite in a loop (
for i in $(seq 20); do ... vendor/bin/phpunit; done) rather than the single test; the 1-in-4 figure was under full-suite conditions only. A run under--repeatof just this file has not reproduced it.