Skip to content

Make ExitWaiter.doExit idempotent to avoid double-resuming continuations#1861

Open
radheradhe01 wants to merge 1 commit into
apple:mainfrom
radheradhe01:fix/exitwaiter-idempotent-doexit
Open

Make ExitWaiter.doExit idempotent to avoid double-resuming continuations#1861
radheradhe01 wants to merge 1 commit into
apple:mainfrom
radheradhe01:fix/exitwaiter-idempotent-doexit

Conversation

@radheradhe01

Copy link
Copy Markdown
Contributor

Problem

ExitWaiter.doExit resumes every stored continuation but never guards against being called twice and never clears the array:

public func doExit(exitStatus: ExitStatus) {
    for cc in continuations {
        cc.resume(returning: exitStatus)
    }
    self.exitStatus = exitStatus
}

doExit can be reached more than once for the same process. In the exec path, the registered onExit callback calls releaseWaiters (→ doExit) and then performs further work that can throw (process.delete()); ExitMonitor.track catches that error and invokes the same onExit again, calling doExit a second time. Resuming a CheckedContinuation twice is a fatal trap.

Fix

Make doExit idempotent: return early if the exit has already been recorded, set exitStatus first, and clear the continuations before resuming them so they cannot be resumed again.

Notes

All access is serialized on the owning actor; the guard simply makes the "exit fires once" invariant explicit and crash-safe.

### Problem
`ExitWaiter.doExit` resumes every stored continuation but never guards against being called twice and never clears the array:

```swift
public func doExit(exitStatus: ExitStatus) {
    for cc in continuations {
        cc.resume(returning: exitStatus)
    }
    self.exitStatus = exitStatus
}
```

`doExit` can be reached more than once for the same process. In the exec path, the registered `onExit` callback calls `releaseWaiters` (→ `doExit`) and then performs further work that can throw (`process.delete()`); `ExitMonitor.track` catches that error and invokes the **same** `onExit` again, calling `doExit` a second time. Resuming a `CheckedContinuation` twice is a fatal trap.

### Fix
Make `doExit` idempotent: return early if the exit has already been recorded, set `exitStatus` first, and clear the continuations before resuming them so they cannot be resumed again.

### Notes
All access is serialized on the owning actor; the guard simply makes the "exit fires once" invariant explicit and crash-safe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant