Skip to content

Wave 3 (0.5.0): JSClockSource seam and deterministic scheduler tests - #20

Merged
ikorich merged 3 commits into
mainfrom
feature/wave3-scheduler-isolation
Jun 11, 2026
Merged

Wave 3 (0.5.0): JSClockSource seam and deterministic scheduler tests#20
ikorich merged 3 commits into
mainfrom
feature/wave3-scheduler-isolation

Conversation

@ikorich

@ikorich ikorich commented Jun 11, 2026

Copy link
Copy Markdown
Member

Motivation

Wave 3 first slice (issue #14, target 0.5.0). JSScheduler's timing logic was only testable against real JS timers with tolerance bands; its environment coupling (JSDate/JSTimer) was hard-wired. This PR introduces the injectable clock seam designed in the modernization analysis — strictly additive — and makes scheduler logic deterministically testable on the host lane.

Changes

  • JSClockSource protocol + DefaultJSClockSource (new JSClockSource.swift): now + makeTimer(millisecondsDelay:isRepeating:callback:) mirroring JSTimer's shape; the default reproduces today's JSDate.now()/JSTimer behavior exactly. All scheduling paths (immediate, one-shot, repeating — and transitively sleep(for:)/timer(interval:)) route through the seam.
  • Additive init(clock:) — a separate initializer, not a defaulted parameter (a default would remove init()'s mangled symbol; verified with the digester).
  • VirtualClock test double + 11 deterministic test variants (exact-value, host-runnable) of the timing suite, plus 2 wasm-gated tests proving the async APIs are driven by the injected clock. Existing wasm real-timer tests stay as integration coverage.
  • Isolation model documented: non-Sendable contract + seam-isolated environment access. @unchecked Sendable evaluated and rejected — since Dual-backend support: #if canImport(Combine) shim for Apple platforms + WASM OpenCombine #11, Apple builds use native Combine, which may legally call a Sendable scheduler from arbitrary threads, so the single-thread JS invariant cannot back the guarantee there. Strict-concurrency (-strict-concurrency=complete): 0 diagnostics before and after.
  • DocC "Deterministic testing" section; CHANGELOG Unreleased (0.5.0).

Test plan

  • Host: 24/3 → 35 tests / 6 suites pass (Combine backend).
  • Wasm: 68/18 → 81 tests / 22 suites pass (js test under Node, re-run stable).
  • API gate: diagnose-api-breaking-changesNo breaking changes detected (gate should be green).
  • swiftformat --lint and DocC build clean.

Closes #14

ikorich added 3 commits June 11, 2026 22:24
JSScheduler held an undocumented hard dependency on JSDate.now() and
JSTimer, making every timing behavior untestable without a live JS
runtime and leaving the single-thread invariant invisible to the type
system. All time observation and timer creation now flow through a
single injectable seam (JSClockSource + JSClockCancellable token),
with DefaultJSClockSource reproducing the historical behavior bit for
bit. The additive init(clock:) is a separate initializer rather than a
defaulted parameter so the existing init() keeps its mangled signature
(API digester stays green). The class doc now records the chosen
isolation model: non-Sendable by contract — @unchecked Sendable was
rejected because Apple-side Combine may call a Sendable scheduler from
arbitrary threads, where the JS event-loop invariant does not hold.
Timing-sensitive scheduler behavior was previously only testable on
wasm with real timers and jitter tolerance bands. VirtualClock is a
manually advanced JSClockSource (due-time order, creation-order ties,
JS clamping semantics) needing no JS runtime, so the new exact-value
variants of the SC-xx timing tests (VC-01..VC-11) run on the host lane
too — host coverage grows 24 to 35 tests. Two wasm-gated tests
(VC-A1/VC-A2) prove the async sleep/timer bridges are driven by the
injected clock end to end. The real-timer wasm suites are kept
unchanged as integration coverage.
The seam only pays off if consumers can discover it: the module
overview now explains how to inject a manually advanced clock for
deterministic, runtime-independent scheduler tests, the components
table and a new Clock Injection topic group surface JSClockSource /
JSClockCancellable / DefaultJSClockSource / init(clock:), and the
CHANGELOG opens the Unreleased (0.5.0) section recording that the #14
surface is strictly additive and why @unchecked Sendable was rejected.
@ikorich ikorich added enhancement New feature or request test suite Testing infrastructure and coverage API design Public API shape and stability labels Jun 11, 2026
@ikorich
ikorich merged commit 4300062 into main Jun 11, 2026
5 checks passed
@ikorich
ikorich deleted the feature/wave3-scheduler-isolation branch June 11, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API design Public API shape and stability enhancement New feature or request test suite Testing infrastructure and coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JSScheduler: concurrency-safe refactor (isolation model + virtual clock seam)

1 participant