13. Concurrency Test Audit¶
This ledger records the controlled-interleaving work for issue 348. It is a source and test audit at the issue branch revision, not a claim that remote CI, publication, or every possible runtime schedule has completed successfully.
13.1. Scope And Evidence Standard¶
The audit traced lifecycle publication, notification waiting, token-pager hub draining, and command admission through the implementations and tests in all five flavors. A deterministic regression must positively acknowledge the boundary that establishes its ordering, retain ownership of spawned work and errors, release its gates on failure, and observe completion. Time bounds are diagnostics and external containment. They are forbidden as successful proof that an operation entered a wait.
The selected negative controls alter the actual wait or cancellation-forwarding
operation, preserve a byte-exact original, run under a process-group watchdog,
and restore in finally. A control is rejected if it compiles no test, matches
no test, fails for another assertion, crashes, or times out when assertion-level
evidence is required. Deliberate deadlock mutants are recorded as externally
terminated outcomes, not as successful pending assertions.
13.2. Five-Flavor Disposition¶
| Flavor | Exact location and schedule | Disposition | Evidence boundary and limit |
|---|---|---|---|
| C# | langs/csharp/tests/VMx.Tests/Components/ComponentVMLifecycleRaceTests.cs, Foreign_Dispose_Waits_For_Ordinary_Lifecycle_Publication; ComponentVMBase.AwaitLifecycleDeliveryUnlessCyclic |
Fixed | The test observes waiter registration on the exact terminal delivery event before checking that disposal and terminal publication remain pending. Removing the production wait fails the selected regression. Reflection of ManualResetEventSlim.Waiters is validated by the net8.0, net9.0, and net10.0 test targets; it does not cover the separate lifecycle-hook wait. |
| Python | langs/python/tests/unit/components/test_lifecycle_race.py, test_foreign_dispose_waits_for_ordinary_lifecycle_publication; _ComponentVMBase._await_lifecycle_publication |
Fixed | A private test wrapper signals entry and delegates to the exact publication event wait. The coordinator waits for that signal, owns every future/error, and releases in cleanup. This proves VMx invoked the instance wait, not the standard library's internal implementation. |
| Rust | langs/rust/src/notifications.rs, post_waiter_remains_pending_until_resolve; langs/rust/src/token_paging.rs, token_cross_thread_hub_drainer_allows_reentrant_refresh_and_disposal; runtime::wait |
Fixed with explicit boundary | A scoped cfg(test) observer acknowledges the exact condition identity; command collectors retain real fire-and-forget handles and raw results. The pager proves foreign hub-owner waiting before enqueue, then active-commit-owner reentrant refresh/disposal. It does not prove the separate hub-delivery-context branches; branch-only removal survived and remains an unproved runtime hypothesis. |
| Swift | langs/swift/Tests/VMxTests/AsyncRelayCommandTests.swift, testCmd012ImmediateCancelDuringAdmissionIsNeverLost; AsyncRelayCommand.executeAsync |
Fixed, supported-Xcode delivery gate required | Synchronous canExecuteChanged cancellation forces the window after executing state changes and before the body cancellation handle is installed. Before delivery, the supported-Xcode gate must record five normal passes, five selected assertion failures with pending forwarding removed, source restoration, and a rebuilt pass; the issue 348 delivery record is the place to record those observed outcomes. The lifecycle blocked-wait oracle and fire-and-forget wrapper completion remain follow-up robustness work. |
| TypeScript | langs/typescript/tests/globalSetup.ts; tests/dist-build/instanceof.test.ts; lifecycle, notification, token-paging, and command suites |
Build ordering fixed; runtime schedules retain follow-up weak oracle | Global setup completes build and fixture/schema synchronization before workers; reruns await the previous run and invalidate generated modules without watcher feedback. Deferred lifecycle scheduling, synchronous delivery and explicit promises control runtime boundaries. Command event-loop drains still have the weak-oracle limitations below. |
13.3. Comparable Tests And Remaining Work¶
The focused fixes do not turn every concurrent test into the same mechanism. The items below are public, independently actionable follow-up records. A bounded wait may diagnose a missing positive event; it does not establish that another worker is blocked. None of the follow-up or weak-oracle items is a new shipping defect without a reproduction at the named boundary.
13.3.1. C#¶
- Follow-up robustness:
langs/csharp/tests/VMx.Tests/Components/ComponentVMLifecycleRaceTests.csOpposing_Active_Lifecycle_Hooks_Cross_Dispose_Without_Deadlock(line 39),Concurrent_Construct_Is_Rejected_While_First_Construct_Is_In_Flight(line 371),Opposing_Lifecycle_Observers_Can_Dispose_Each_Other_Without_Deadlock(line 569), andConstructAsync_Faults_When_Background_Child_Rolls_Back(line 330) use genuine hook/barrier or release-gate ordering. Their limits are unchecked barrier/release results or scheduler errors and worker cleanup that can be skipped after a failed assertion. Make release and join ownership unconditional before strengthening their outcome assertions. - Stress, not a forced schedule: the same file's
Background_Construct_Racing_Dispose_Never_Resurrects_Or_Publishes_PostDispose(line 494) and theConcurrent_Dispose_Invokes_OnDispose_At_Most_Oncecase are useful invariant sampling, but a start gate does not select a buggy interleaving. Keep them as stress coverage; add a boundary observer only if a particular wait/order contract is being claimed. - Weak oracle:
langs/csharp/tests/VMx.Tests/Commands/ConfirmationDecoratorCommandTests.csDispose_Waits_For_InFlight_Error_Delivery(line 34) signals beforeDisposeand infers contention from elapsed time. Its actual shared boundary is_errorGateinlangs/csharp/src/VMx/Commands/ConfirmationDecoratorCommand.cs. It needs a monitor-admission strategy andfinally-owned worker completion; the lifecycle event-waiter technique is not applicable toMonitor.Enter. - Follow-up robustness:
langs/csharp/tests/VMx.Tests/Commands/AsyncRelayCommandTests.csPredicate_Can_Wait_For_Foreign_Dispose_Without_Deadlock(line 27), andlangs/csharp/tests/VMx.Conformance.Tests/CommandConformanceTests.csCMD_012_Cancel_Cancels_InFlight_Async_Task_NonThrowing(line 284) andCMD_012_Concurrent_ExecuteAsync_Does_Not_Double_Run(line 405), use positive state/progress checks. Retain them, but collect raw worker errors and release awaited tasks on every failure path. The notification callback testOpposing_Hub_Callbacks_Do_Not_Deadlock(line 227) and token-pager testsRefresh_Supersedes_An_Older_InFlight_LoadMore(line 134) andRefresh_PageComparer_DoesNotRunUnderStateGate(line 253) likewise need failure-path task ownership; they are not evidence of a competing-thread drainer wait.
13.3.2. Python¶
- Weak oracle:
langs/python/tests/unit/components/test_lifecycle_race.pytest_foreign_dispose_waits_for_ordinary_lifecycle_publicationis repaired, but the nearby disposer-from-_set_statuscase (lines 223--259) still treats an unobserved race as acceptable. Its retained limit is that a thread-start signal does not prove ownership-lock admission; add that exact boundary observer before treating it as a regression. - Follow-up robustness: the same module's
test_opposing_lifecycle_observers_do_not_deadlockandtest_opposing_active_lifecycle_hooks_cross_dispose_without_deadlockcases, plus its 8,000-iteration stress loop, establish callback overlap or invariants but leave raw worker errors and failure-path joins incomplete. Keep the stress case as supplementary coverage; make barrier results, release, and joins owned by cleanup. - Follow-up robustness:
langs/python/tests/unit/commands/test_async_relay_command.pylines 182--224, 260--405 andlangs/python/tests/unit/commands/test_confirmation_decorator_command.pylines 95--125 use positive completion/entry signals. The short sleeps are polling backoff or loop turns, not blocked-thread proof; capture worker exceptions and release/await tasks on assertion failure. - Valid controlled schedule with cleanup follow-up:
langs/python/tests/conformance/test_col_024_to_031_token_paged_composition.pylines 138--167 explicitly gates fetch entry and release, so it controls the refresh-before-old-result order. Ensure both tasks release and finish in cleanup. Its lines 49--76 and 171--190 useasyncio.sleep(0)only to drain an event-loop turn; replace it with fetch-entry acknowledgment if an in-flight admission claim becomes material. - Follow-up weak oracle:
langs/python/tests/conformance/test_commands.pylines 306--394 and the selected confirmation tests use cancellation targets or event-loop drains, not native-thread synchronization. Directly await positive error/completion events where that is the intended assertion. The worker-posted and opposing callback cases intest_notifications.pylines 406--465 retain worker error/join ownership work; no runtime fault is established.
13.3.3. Rust¶
- Weak oracle:
langs/rust/tests/conformance/lifecycle.rsforeign_disposal_waits_for_an_admitted_lifecycle_hook(line 47) andconcurrent_admission_cannot_escape_disposal_snapshot(line 719) use a thread-entry signal followed by a negative 50-ms receive. Add actual lifecycle-hook/disposal-admission boundary acknowledgments and a release guard.disposed_parent_skips_post_hook_child_construction(line 91) has stronger positive state evidence but still needs failure-path release/join; the opposing-hook/callback tests (lines 132--167 and 491--549) retain bounded cleanup work. - Weak oracle:
langs/rust/tests/conformance/notifications.rsconcurrent_pending_delivery_matches_committed_snapshot_order(line 128),concurrent_resolve_returns_after_its_queued_snapshot_and_waiter_completion(line 197), andconcurrent_dispose_returns_after_queued_terminal_publication_and_completions(line 501) observe committed state before a negative receive. ObservePendingPublicationCompletion::waitinlangs/rust/src/notifications.rsdirectly, bound polling, and make gates releasable on failure.concurrent_notification_hub_dispose_publishes_one_terminal_snapshot(line 565) is stress/exclusivity coverage, whilepost_racing_dispose_never_orphans_its_waiter(line 593) leaves its waiter thread untracked; neither proves a selected schedule. - Weak oracle or stress:
langs/rust/tests/conformance/collections.rsserviced_collection_foreign_publisher_waits_and_delivers_on_caller_thread(line 448) starts the foreign publisher before inferring it is blocked; instrument the actual delivery-admission boundary. The 32-workerkeyed_serviced_concurrent_upserts_publish_each_committed_add_position(line 1015) andserviced_collection_concurrent_pushes_publish_in_committed_position_order(line 1050) are stress tests.token_paging_dispose_wins_against_an_in_flight_loader(line 1613) has a real loader boundary but still needs a release/join guard. - Weak oracle:
langs/rust/src/token_paging.rsnested_commits_keep_foreign_disposers_waiting_for_the_outer_guardneeds acknowledgments for bothrequest_disposewaits and outer-guard cleanup. Incommands.rs,async_relay_command_cancel_cancels_in_flight_task(line 425) andrepeated_async_command_dispose_cancels_one_in_flight_execution(line 656) use polling sleeps/unboundedis_executingspins; use entry/release channels and bounded cleanup.async_relay_command_immediate_cancel_is_not_lost_during_admission(line 452) is useful bounded stress, not a forced admission boundary, and the detachedcan_executeworker at lines 395--397 must be joined. - Unproved runtime hypothesis: the repaired pager test demonstrates the selected foreign-hub-owner wait and reentrant commit path only. Mutants that remove separate hub-delivery context remain outside that proof; deliberate cyclic-lock mutants require subprocess containment rather than an in-process join timeout.
13.3.4. Swift¶
- Weak oracle and cleanup follow-up:
langs/swift/Tests/VMxTests/LifecycleRaceTests.swifttestDisposeDuringReconstructDestructHookSkipsConstructPhase(line 240) acknowledges the destruct hook but uses a completion-semaphore timeout for the disposer. Instrument the foreign-hook wait inlangs/swift/Sources/VMx/Lifecycle/ComponentVMBase.swift, propagate the reconstruct result, and release/join both participants on every path. ItstestBackgroundConstructRacingDisposeNeverResurrectsOrPublishesPostDispose(line 406) is bounded stress despite its loop, andtestOpposingLifecycleObserversDisposeEachOtherWithoutDeadlock(line 80) plustestOpposingActiveLifecycleHooksCrossDisposeWithoutDeadlock(line 126) retain unbounded group waits and swallowed worker errors. - Follow-up robustness:
langs/swift/Tests/VMxTests/AsyncRelayCommandTests.swiftCancellationRaceGate.waitUntilObserved(line 77) and the external-first/command-first tests (lines 441--469) have real cancellation ordering but need bounded, independently releasable cleanup.testCmd012ExecuteDoesNotRouteCancellationToErrorsWhenThrowOnCancelIsSet(line 496) uses a 200-ms inverted error expectation (lines 499 and 517) before wrapper error routing is known; acknowledge the fire-and-forget wrapper's error decision, then count errors. - Follow-up robustness:
langs/swift/Tests/VMxTests/NotificationHubConcurrencyTests.swifttestPostThenResolvePublishesInMutationOrder(line 148) andtestPostThenDisposePublishesSnapshotBeforeCompletion(line 199) correctly useOneShotDrainGate(line 28),beforeDeliveryDrain, andafterDeliveryEnqueued, but its waits and task completion need abort/release cleanup.testOpposingHubCallbacksResolveWithoutDeadlock(line 251) must retain/join its callback task; an irrecoverable deadlock mutant belongs in an externally bounded process. - Valid controlled schedule with cleanup follow-up:
langs/swift/Tests/VMxTests/TokenPagedCompositionTests.swifttestRefreshSupersedesAnOlderInFlightLoadMore(line 134) andtestRefreshDoesNotMutateOrNotifyWhenDisposedDuringFetch(line 172) force fetch entry through continuations. Give their continuation owner an abort state so failure cannot strand registered or late waiters, then dispose and join outstanding work. - Separate unproved runtime hypothesis:
examples/swift/notes-showcase/Tests/NotesShowcaseTests/NotesViewVMTests.swifttestReconstruct_keeps_items_via_rebind(line 337) is associated with a recorded index-out-of-range crash, but no application stack identifies the access.ImmediateDispatcherand zero-debounce search may overlap reconstruct with callback work; this is not a proven lifecycle-locking fix. A separate investigation must capture the application stack or force that overlap, then select one executor/scheduler policy and drain/cancel search work during cleanup. Do not mask it with retries, delays, or a disabled test.
13.3.5. TypeScript¶
- Fixed build/test ordering:
langs/typescript/tests/globalSetup.tsbuilds before test workers, replacing the build insidetests/dist-build/instanceof.test.ts. The oldbeforeAllran npm'sprebuildfixture/schema copies while other suites imported the same source JSON. During promotion, Windows reportedEBUSYopening the consumer schema; that log does not prove which process held the OS lock. A separate boundary guard positively identified the worker-originated schema write. The setup orders this writer before readers. Watch reruns await the preceding run and rebuild current inputs. The watcher ignores generated outputs, and setup explicitly invalidates old and new output paths so cached ESM transforms cannot hide the rebuilt content. Automatic test selection is unchanged; manual reruns cover canonical spec inputs outside the import graph. Independent external builds are not serialized by this setup. - Valid controlled schedules:
langs/typescript/tests/unit/lifecycleRace.test.tsComponentVM – dispose during in-flight background construct(line 28) controls a deferred dispatcher;langs/typescript/tests/unit/notifications/notificationHub.test.tsNotificationHub pending delivery(line 10) checks synchronous/reentrant ordering; andlangs/typescript/tests/conformance/col-024-to-031-token-paged-composition.test.tsuses explicit fetch promises for the load/refresh and reentrant-comparer cases (lines 48--69, 131--190). Preserve these schedules and add owned settlement/subscription cleanup when touching them. - Follow-up weak oracle:
langs/typescript/tests/conformance/commands.test.tsCMD-012 cases at lines 604--669, andlangs/typescript/tests/unit/confirmationDecoratorCommand.test.tsexecute() is safe when confirm rejects(line 8) plus the two VMX-009 error cases (lines 37 and 52), use zero-delay timers at lines 636, 665, 21, 48, and 65. These are event-loop-turn drains, not foreign-thread evidence. Subscribe to the expected positive error beforeexecute()and await it with a diagnostic timeout where the error is expected. The no-error CMD-012 case still needs a terminal wrapper/error-routing acknowledgment; idle state alone is not that acknowledgment becauseasyncRelayCommand.tsclears it before error routing. Do not replace the timers with an arbitrary single promise hop or add a public API solely for this test.
13.4. CI Evidence Contract¶
Ubuntu C#, Python, and Rust jobs run the selected command three times normally
and three times with taskset restricted to one CPU from
os.sched_getaffinity(0). Each invocation has a 120-second command timeout and
five-second kill grace, prints its mode and run number, and fails the step on
the first unsuccessful run. Python retains the selected matrix interpreter
through tools/check-python-interpreter.py. C# targets the unit project across
all three target frameworks. Rust repeats the locked all-features full suite,
and the package matrix compiles and runs cfg(test) unit helpers with Rust
1.88.0 before preserving the existing package and consumer checks.
The macOS Swift workflow runs the focused test five times, removes only pending
admission-cancellation forwarding, and requires five exit-1 runs whose sole
XCTest diagnostic is the stable selected cancellation assertion. It restores
the exact production bytes in finally, rebuilds, and requires a final pass.
Every child starts in a new process group; a 120-second watchdog kills the whole
group and fails the experiment. Supported-Xcode execution is a mandatory
delivery gate: before publication, issue 348 must record its observed outcome
(including normal, mutant, restoration, parser, and watchdog evidence). This
ledger defines the required check and does not claim that a remote run has
already occurred.
13.5. Unchanged Contract And Cascade¶
The work changes test scheduling and CI evidence only. Public APIs, normative
behavior, the 403 library conformance IDs, five scenario IDs, specification
chapters and ADRs, spec/VERSION, flavor package versions and minimum-spec
declarations, compatibility matrix, changelogs, shared fixtures, package
dependencies, and package inventories are unchanged. Existing conformance
markers remain attached when the two Rust regressions move from integration to
unit scope; neither moved test owned a catalog marker.
No production caller or sample requires migration: the C# and Python wait
observers are private test mechanisms, Rust helpers and collectors compile only
under cfg(test), and Swift uses existing public command behavior. TypeScript changes only its
Vitest build preparation and watch-cache coordination. The C#,
Python, TypeScript, Swift, and Rust examples therefore retain their APIs and
source. Documentation is canonical in docs/content and this maintenance
ledger; repository site and wiki projections are generated through the existing
manifest pipeline. Live site/wiki publication and remote rendered review remain
delivery steps after the branch gates pass.