Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Sources/SharedLiveAgentIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,26 @@ final class SharedLiveAgentIndex {
_ = await applyPendingForkValidations(
pendingRequestIDsToRemoveOnCancellation: pendingRequestIDsOwnedByRequest
)
// The pass above may find this caller's request already claimed by
// another drainer: the unguarded tail restart in
// `applyPendingForkValidations` can spawn a detached refresh that
// wins the race against a contention waiter it just resumed, and
// that waiter then returns to an empty queue. Returning here would
// break this method's contract -- the queued validation must be
// applied before it returns -- so callers could read stale fork
// availability.
//
// This is a symptom fix, not the root cause. The root cause is that
// the tail restart in `applyPendingForkValidations` lacks the
// `!resumedWaiters` guard its in-loop sibling has, so it can resume
// a waiter and then immediately race it. Guarding it there is the
// real repair, but the obvious form can strand a pending request
// when the resumed waiter's task is cancelled right after resuming,
// so it needs its own change. The live-index branch below has the
// same hole when `didReload` is true -- `reload()` runs
// `applyPendingForkValidations` internally, so the same steal can
// happen and that path returns without waiting.
await waitForForkValidationRequestCompletions(pendingRequestIDsOwnedByRequest)
return
}
let reloadResult = await reloadIfLiveAgentProcessFingerprintChanged(
Expand Down
Loading