Repository navigation
Bound WASM Worker startup separately from the policy deadline (#727) - #728
Conversation
A fresh module Worker's cold start counted against the 250 ms policy deadline, so a correct cold Firefox load on a hosted 4-vCPU runner was refused with DeadlineExpired. The load command is still posted at Worker creation, but the Worker's first Compile receipt is now its readiness report: until then the load control is in a `start` phase under a separate startup bound (default 5000 ms, HostSettings.withWorkerStartTimeout, capped by any enclosing deadline), whose expiry is the distinct, appended DeliveryReason.WorkerStartTimeout. Readiness starts the unchanged policy budget. Receipts before readiness are inert. The canonical lifecycle model, its boundary/compatible schedules, generated traces (default 58, selected 16), F# correspondence and unit tests agree. The producer browser suites run in Chromium, Firefox and WebKit, with a cold-load case recording per-browser startup and policy timings under a 4-CPU budget. Coherent version 0.7.0, classified against published 0.6.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Firefox's reduced timer precision lets a setTimeout callback observe floor(performance.now()) <= due (measured 36/300 callbacks). The connected interpreter then delivered a timer observation that expired nothing and was never re-armed, so a looping guest could hang unretired (seen as intermittent 15 s hangs of the infinite Process/Free/Shutdown watchdog cases in Firefox). The interpreter now delivers a timer observation only after the transport clock passes its due time and re-arms the remainder otherwise. WebKit reports a wasm trap as "Unreachable code ...", so trap diagnostics are compared case-insensitively. The one-millisecond policy-budget case accepts expiry before or after the withheld genuine loaded receipt is produced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mer lessons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review: PR #728 at
|
| Browser | Startup p50 | Startup max | Startup max ÷ 5000 | Policy max | Policy max ÷ 250 |
|---|---|---|---|---|---|
| Chromium 153 | 27.5 ms | 37 ms | 135× | 5.7 ms | 44× |
| Firefox 155 | 58 ms | 76 ms | 66× | 8 ms | 31× |
| WebKit 26.6 | 44 ms | 52 ms | 96× | 9 ms | 28× |
Model and implementation agreement
lifecycle.qnthas thestartphase andrequestForusesworkerStartBound. Timer effects projectstartascompile, which matches the F#TimerIdentity. Readiness setsdeadline = clock + policyBudget; the compat module usesdueForwith the selected limits andstartDueForcapped by enclosing.compiledis inert while starting. Expiry givessettleStartTimedOutwithWorkerStartTimeout/TimedOut/compile/not dispatched.- New invariants:
startingHasNoAuthority,deadlinesBounded,startTimeoutIsDistinctandstartTimeoutTerminatesinlifecycleSafe, andselectedStartingHasNoAuthorityincompatibilitySafe. No existing invariant was removed. - Five named Quint tests were added; the preflight count goes from 8 to 13 and the expected set is updated. Two witnesses were added:
sawWorkerReadyandsawStartTimeout. - Schedule changes only insert a readiness step: each schedule gets
if starting → readyNow, and its step count rises by the number of fresh Workers. There are five new deterministic schedules (census 53/16 → 58/16, 71 → 76 paths). The readiness, expiry and inert-compile sequences in the new traces read correctly. - Mutants added:
startup-expiry-as-policy-expiry,readiness-keeps-startup-deadlineand rawstartup-cause-to-deadline. No existing mutant or witness was removed. verify-wasm-supervisor.shadds areadyCandidateCompatiblesampled entry, appended tomodel-ready.log. This adds samples and relaxes no gate: the witness set and the[1-9]threshold are unchanged. It is justified because the extra readiness step madesawFrozenCommitExpiryRefusedunreachable at the pinned seed, where it had been a 1-in-1000 sample. That is better than changing the seed.
Tests
-
Unit tests. New tests cover:
- the startup bound for each profile, including the exact boundary, late readiness and termination;
- readiness starting the policy budget, a duplicate readiness and policy expiry staying
DeadlineExpired; - pre-readiness, foreign and wrong-correlation inertness;
- range validation and compatibility retention;
- the enclosing cap;
- candidate preservation;
- the early-timer re-arm;
- disposal during startup.
Existing fixtures only gained the readiness step.
-
Browser specs.
- The 1 ms compile-deadline test became a deterministic withheld-
loadedtest with stronger reason/disposition assertions. Itsheld ∈ {0,1}andPhase ∈ {Compile, Instantiate}checks reflect a real race, not a weakening. - The new withheld-readiness test exercises inert pre-readiness
loadedreceipts in a real browser. - Trap wording is now compared case-insensitively, which is a justified WebKit difference.
loadBounded→loadLimitedin the fresh-Worker tests follows from the documented design: an enclosing deadline caps startup.
- The 1 ms compile-deadline test became a deterministic withheld-
-
Cold load.
- Each sample uses a fresh context, so there is no HTTP, module or compile cache, but the browser process is warm after the first sample.
- Startup is measured as Worker creation to the readiness wire. Policy is measured as readiness to settlement. The two are measured separately and correctly.
- It asserts
n ≥ 20, success, the advice bytes and max values under both bounds.
-
Release-check updates. Playwright install moved to three engines in both
install-browser-tools.shand the workflow, with the matching step-name mutant updated inverify-wasm-workflow.sh. The cold-load artifact is retained withif: always().
Release coherence
- Everything moved to 0.7.0 together:
version.props,sdk/wasm/VERSION, the Rust crates/locks, the worker bundle,compatibility-profile.v1.json,promotion.pyandqualify-supervisor-installed.sh. api-baseline-policy.jsonis rebased on published 0.6.0, and I checked each value independently:- The tag
wasm/v0.6.0resolves to commit8a5e63b…and tree090906c…. - The release asset digest of
release-manifest.jsonise40258fe…. - The nuget.org served archives hash to
4245920d…(Contracts) ande869095e…(Browser).
- The tag
- The historical policy is kept as
api-baseline-policy-0.6.0.json(byte-identical to the former current) and is registered inrelease_manifest.py. bind-installed-release.pystill binds published 0.6.0. That is correct, and matches earlier bumps: the binder moves only after publication.- No tag or publication is created.
CI on the exact head
gh pr checks 728: 34 pass, 1 skipping. The skipped job, "Prepare canonical WASM trace artifact (not qualification)", is skipped by design.- wasm-shared run 39881900333 (headSha
1f68f96): success. It took 26 m 35 s against the job'stimeout-minutes: 35. Every step passed, including Quint/correspondence, raw conformance in three browsers, package consumer plus cold-load in three browsers, and compatible supervisor source and installed custody.
Non-blocking notes
- Docs wording.
docs/wasm/runtime.mdsays "other receipts before readiness … are inert". That is true for phase and ABI receipts. It is not true forWorkerFailed,WorkerTerminatedor a protocol-violatingInvocationObserved/InvocationTerminatedon the load control, which still fault or invalidate the starting Worker. That behavior is correct (faults should act before readiness); only the wording overstates. - Selected-policy coverage of startup expiry. Under
Sc2SupervisorV1, startup expiry is never sampled: compat loads useenclosing: 0andcompatibleTimeradvances +1. No selected deterministic schedule or F# expiry test covers it either. Also,deadlinesBoundedis not part ofcompatibilitySafe. The risk is low because the F# expiry path is shared, but one selected schedule would close the gap. - Enclosing deadlines on loads. A caller enclosing deadline that elapses before readiness reports
WorkerStartTimeout. This is consistent with the documented design. It also means a receiver that passes a short enclosing deadline (for examplenow + 250) on a load would still bound Worker startup by it. The Templates re-pin should not do that. - CI headroom. The wasm-shared job now takes about 26.5 of its 35 minutes, leaving about 8.5 minutes of headroom after adding two engines and 60 cold loads.
WorkerEntry.inspectAndCompile(public). It emitsCompileonly after compiling. A custom Worker built on it would report readiness late. The packaged Worker does not use it.- Naming.
EndToEndFromEnqueuenow starts counting at readiness for loads. The docs state this, but the name is now technical debt. - Early-timer path. It builds a full
Projectionon each early callback. That is cheap but avoidable.
fdev critic
Compilation, phase and digest receipts before readiness are inert; a Worker failure, termination or protocol-violating invocation receipt still faults the starting Worker. A caller enclosing deadline on a load also caps startup. The unmerged Atlas capture is withdrawn here and re-captured against this text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mer lessons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delta review: PR #728,
|
The Firefox/WebKit system-package download took 16m28s on one hosted run (41s on the previous), cancelling run 39884877948 at the 35-minute job limit after every earlier gate passed. Raise the qualify job limit to 55 minutes and bound the browser install step at 20 minutes with one retry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exact-candidate decision: PR #728 at
|
| Browser | Startup max | Policy-load max |
|---|---|---|
| Chromium | 35.8 ms | 5.7 ms |
| Firefox | 100 ms | 5 ms |
| WebKit | 61 ms | 8 ms |
Firefox's 100 ms startup still leaves 50× margin under the 5,000 ms bound, and every policy load is at least 31× under 250 ms.
Material findings
None.
Non-blocking notes
- Slow-mirror case. At the 125 kB/s seen in run 39884877948, a 119 MB fetch needs about 16 minutes. A single 9-minute attempt would therefore time out. The retry is likely to succeed because apt keeps fully downloaded
.debfiles in its cache, but that is marginal. The outcome is now a clear install-step failure rather than a late job cancellation, which is acceptable. - Interrupted install on retry.
timeoutsends its signal to the whole process group, so it can interruptapt-get/dpkgmid-install. The retry may then hit an interrupted dpkg state. This is rare; asudo dpkg --configure -abefore the retry would harden it. - Preflight coverage. The workflow preflight pins the 55-minute job limit but not the install step's bound and retry tokens.
- My earlier non-blocking notes on
1f68f96still stand: selected-policy coverage of startup expiry, theEndToEndFromEnqueuenaming,inspectAndCompilereporting readiness after compile, and the early-timerProjectioncost.
fdev critic
Refs #727 (source slice only; publication needs a separate fdev admission).
What
Implements fdev's decided direction for #727:
Compilereceipt for that command is its readiness report. Before readiness the load control is in phasestartunder a separate startup bound — default 5,000 ms (HostSettings.defaultWorkerStartTimeoutMilliseconds), selectable 1–60,000 ms viaHostSettings.withWorkerStartTimeout, capped by any caller enclosing deadline. Expiry before readiness settlesDiscardedwith the distinct, appendedDeliveryReason.WorkerStartTimeout(outcomeTimedOut/Compile/not dispatched), terminates the Worker and grants no game authority. Readiness replaces the startup timer with the unchanged 250 ms policy timer. Compilation, phase and digest receipts before readiness, repeated readiness and readiness after expiry are inert; a Worker failure, termination or protocol-violating invocation receipt before readiness still faults the starting Worker. Requests to ready Workers, disposal, replacement, candidates, freeze and terminal retirement are unchanged.eng/wasm-shared/lifecycle.qntgains thestartphase, readiness,settleStartTimedOut, and invariantsstartingHasNoAuthority,deadlinesBounded,startTimeoutIsDistinct,startTimeoutTerminates; five new named Quint tests; five new deterministic correspondence schedules (startup boundary before/at/after, early compile receipt, candidate startup refusal); regenerated traces (default 58 / selected 16); two new causal mutants plus one raw-terminal mutant.wasm-cold-load-*artifact.Startup bound rationale and test evidence are in
docs/roadmaps/wasm-shared-01.mdand the PR thread.Signed: fdev
🤖 Generated with Claude Code