Skip to content

Fix subject ordering during concurrent sends - #62

Merged
twittemb merged 1 commit into
mainfrom
codex/fix-subject-send-ordering-61
Oct 3, 2026
Merged

twittemb merged 1 commit into
mainfrom
codex/fix-subject-send-ordering-61

Conversation

@twittemb

@twittemb twittemb commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Description

Concurrent subject sends can reach subscribers in different orders, leaving current-value and replay subscribers on stale values. Serialize values and termination in a FIFO queue owned by each subject's existing locked state, then drain channel deliveries after releasing the lock. Apply the fix to all six subjects.

Closes #61.

The contributor's original delivery-lock proposal can deadlock when a consumer's cancellation handler sends back into the subject. The revised queue proposal avoids that cycle; this implementation also removes the separate queue lock by keeping queue admission, state updates, and subscriber snapshots in one critical region. Drainer ownership is released atomically with checking for an empty queue, preventing stranded deliveries.

Concurrent send calls may return while another sender drains their queued delivery. State updates and subscriber registration remain synchronous. Document this timing in the README and changelog. Public signatures, dependencies, and deployment targets are unchanged. The internal queue's narrow @unchecked Sendable conformance documents the value-semantics invariant required by the pinned swift-collections 1.0.3 Deque, which lacks its own Sendable conformance.

Validation

Validated on Apple Swift 6.4 / macOS against main at 93d173a, using an isolated copy containing exactly this change; all 12 committed files match the tested snapshot.

  • Before the production fix, the new ordering regressions failed for all six subjects.
  • swift build -Xswiftc -suppress-warnings: passed.
  • swift test --enable-code-coverage -Xswiftc -suppress-warnings: 168 tests passed, zero failures.
  • swift test --filter 'AsyncSubject(ConcurrentSendOrdering|QueuedDelivery|Cancellation)Tests': 24 tests passed, zero failures; no new compiler warnings, with 21 existing unused throwing-task warnings in older tests.
  • Regression coverage includes shared subscriber order, per-producer order and counts, current-value/replay consistency, paused delivery with late subscriptions, values before finish/failure, drainer restart, and cancellation handlers that send back into their subject.
  • git diff --check: passed.

Checklist

  • this PR is based on the main branch and is up-to-date, if not please rebase your branch on the top of main
  • the commits inside this PR have explicit commit messages
  • unit tests cover the new feature or the bug fix
  • the feature is documented in the README.md if it makes sense
  • the CHANGELOG is up-to-date

Queue values and termination alongside subject state updates, then drain deliveries outside the state lock. Preserve a shared subscriber order and current-value/replay consistency without reintroducing cancellation-handler lock inversion.

Add ordering, paused-drainer, and reentrant cancellation regressions for all six subjects. Document queued delivery timing.
@twittemb
twittemb merged commit 900a91d into main Oct 3, 2026
2 checks passed
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.

[BUG] Since #59, concurrent sends reach consumers in different orders and current-value/replay consumers can end on a stale value

1 participant