Skip to content

fix(query): widen the wasm32 clock past Int32.max to survive long uptime#154

Merged
zzal merged 1 commit into
mainfrom
fix/query-clock-wasm32-overflow
Jul 6, 2026
Merged

fix(query): widen the wasm32 clock past Int32.max to survive long uptime#154
zzal merged 1 commit into
mainfrom
fix/query-clock-wasm32-overflow

Conversation

@zzal

@zzal zzal commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Part II Wave 1 · #1 — wasm32 clock trap at ~25 days of uptime

SystemQueryClock.now() read performance.now() and returned .milliseconds(Int(ms)). performance.now() grows monotonically from navigation start; on wasm32 Int is 32-bit, so Int(ms) traps — killing the whole app — once page uptime passes Int32.max ms ≈ 24.85 days. That's exactly the long-lived dashboard workload the cache is built for (MissionControl models it).

Invisible to every existing gate: the host's Int is 64-bit, and the type was smoke-tested in the browser, not unit-tested. (Its sibling RetryPolicy.delay was scrupulously overflow-proven; this one narrowing slipped through — the [[wasm32-int-32bit]] footgun.)

Fix

Extract a pure, host-testable conversion and widen through Int64:

static func duration(millisecondsSinceOrigin ms: Double) -> Duration {
    .milliseconds(Int64(exactly: ms.rounded()) ?? .max)
}
  • Int64 widening removes the 32-bit trap (Int64.max ms ≈ 2.9e8 years — unreachable).
  • Int64(exactly:) ?? .max also absorbs a non-finite reading (which traps even on the host) into a benign maximally-stale → refetch, never a crash.
  • Extracted, not inlined in now(), precisely so the rules can be pinned by host tests the browser-only path can't otherwise reach.

Tests

3 new host tests in ClockTests:

  • widens past Int32.max (2^31 boundary + a 30-day uptime)
  • rounds a sub-millisecond reading to the nearest ms
  • clamps .infinity / .nan to .max instead of trapping

Verification

  • New Clock tests 4/4 pass.
  • Full host suite green: 1373 tests, 275 suites.
  • QueryDemo wasm build clean — compiles the #if canImport(JavaScriptKit) branch that actually carries the fix.

🤖 Generated with Claude Code

SystemQueryClock.now() read performance.now() and did .milliseconds(Int(ms)).
On wasm32 Int is 32-bit, so Int(ms) traps — killing the whole app — once page
uptime passes Int32.max ms ≈ 24.85 days, exactly the long-lived dashboard
workload the cache is built for. The host's 64-bit Int hid the narrowing, so
every existing gate missed it (the type was smoke-tested in the browser, not
unit-tested).

Extract a pure, host-testable duration(millisecondsSinceOrigin:) that widens
through Int64 instead of Int and clamps a non-finite reading to .max
(Int64(exactly: ms.rounded()) ?? .max) rather than trapping — a benign
"maximally stale → refetch", never a crash. Pin the boundary, the sub-ms
rounding, and the non-finite clamp with host tests the browser-only path can't
otherwise reach.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📦 Bundle size

Artifact Baseline This PR Δ
App.wasm 5.06 MB 5.15 MB +1.90%
App.wasm (gzip) 1.80 MB 1.84 MB +2.21%
JS runtime 54.5 KB 54.5 KB +0.00%
JS runtime (gzip) 11.3 KB 11.3 KB +0.00%
Total (gzip) 1.81 MB 1.85 MB +2.19%

✅ Within budget (≤5% growth allowed).

Baseline: Swift 6.3, WASM SDK 6.3-RELEASE, measured 2026-06-18.

@zzal zzal merged commit caa0f92 into main Jul 6, 2026
6 checks passed
@zzal zzal deleted the fix/query-clock-wasm32-overflow branch July 6, 2026 20:42
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