Skip to content

Avoid holding MergeStorage lock across continuation registration - #444

Open
thliu21 wants to merge 2 commits into
apple:mainfrom
thliu21:codex/async-algorithms-430-merge-lock
Open

Avoid holding MergeStorage lock across continuation registration#444
thliu21 wants to merge 2 commits into
apple:mainfrom
thliu21:codex/async-algorithms-430-merge-lock

Conversation

@thliu21

@thliu21 thliu21 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • register MergeStorage downstream continuations after releasing the thread-affine lock
  • handle cancellation, buffered values, upstream completion, and upstream errors that win the registration race
  • add deterministic state-machine coverage for each registration-race outcome

Root cause

MergeStorage.next() previously held its lock while entering withUnsafeThrowingContinuation. Swift 6.0–6.3.3 could re-enqueue before that closure executed, allowing a thread-affine lock to be unlocked from a different thread. The state machine now owns the race window explicitly, so continuation registration is protected by a short lock acquisition instead.

Validation

  • swift test --filter TestMerge — 32 tests passed
  • swift test — 362 tests passed
  • git diff --check

Fixes #430.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should remove all the manual lock/unlock calls now and only use withLock

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also split this function up, as we did for AsyncStream and MPSC Channel.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

// The continuation is registered after `next()` releases the lock. While
// it was unlocked, an upstream may have produced an element or finished.
if let element = buffer.popFirst() {
state = .merging(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a good idea to eventually refactor this into a single type. We can do that in a separate PR.

@thliu21
thliu21 marked this pull request as ready for review July 22, 2026 15:48
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.

Crash: os_unfair_lock unlocked from wrong thread in MergeStorage

3 participants