ADR-0041: Exclusive ownership of a playground scope¶
Status¶
Proposed; implemented locally for validation.
Context¶
Tabs may reuse an explicit scope or inherit sessionStorage on duplication. The
PHP bridge and mutable journal are scoped, while runtime selection is separate.
At 3e257773, every worker listening on the same bridge accepts every request.
Two active workers can execute the same POST, including different PHP versions.
Problem¶
How can shared scopes avoid duplicate mutations without implementing concurrent ownership, changing persisted data, or replacing the Service Worker?
Decision drivers¶
- One active PHP worker per scope, including identical runtime versions.
- Preserve reload, Reset, runtime selection, and existing journals.
- No leases, heartbeats, automatic takeover, or replay of pending mutations.
Options considered¶
Option 1: Include only runtime in bridge names¶
Does not prevent two workers of the same runtime or concurrent journal writers.
Option 2: Coordinate owners and handoff through the Service Worker¶
Requires additional lifecycle and SW-restart state. Unnecessary for an ephemeral playground that does not need simultaneous use of the same scope.
Option 3: Exclusive Web Lock for the remote document¶
The browser arbitrates a named lock and releases it when its owner terminates.
ifAvailable rejects a second owner immediately rather than queueing a future boot.
Evidence¶
src/shared/protocol.js,sw.js:forwardToPhpWorker, andphp-worker.js:installBridgeListenerat3e257773show scope-wide dispatch.tests/e2e/scope-ownership.spec.mjscovers conflicting versions, identical versions, separate scopes, a single POST, owner close, late request IDs, persistence, reload, Reset, runtime selection, and progress.- Web Locks request semantics.
- ADR-0027 and ADR-0035 prohibit automatic mutation replay and define persistence.
Decision¶
Acquire moodle-playground-owner:<scope> before reset, storage access, or PHP
worker creation. Hold the exclusive lock for the remote document's lifetime.
The worker opens its HTTP bridge only after authoritative configuration arrives;
cached URL parameters must not make it listen on another scope during startup.
On pagehide terminate the PHP worker before releasing ownership; drain any remote
preparation still in flight. A restored document reloads rather than resuming a
terminated worker. Browser teardown also releases the lock.
A conflicting tab displays an error and requires an explicit reload after the owner closes. Missing or denied Web Locks fail closed. No IndexedDB deletion is performed by a rejected owner. Reset and runtime changes reacquire ownership.
Give each remote load a new notification channel suffix, propagated through shell, remote, and PHP worker. This separates blocked tabs and old notifications. HTTP reply correlation continues using existing unique request IDs; ownership changes never redispatch pending HTTP requests. Keep scoped routing, cache keys, journal names, and DB paths unchanged.
Consequences¶
Positive¶
- The browser supplies exclusive ownership without application leader election.
- Conflicting tabs cannot create a second PHP worker or reset the active journal.
Negative¶
- Web Locks become a required browser capability; there is no unsafe fallback.
- A rejected tab does not automatically start when another tab closes.
Neutral¶
- This does not provide transactional exactly-once delivery across browser crashes.
- Existing unflushed-write limits and GET/HEAD-only crash replay remain unchanged.
Risks¶
Check page lifecycle and browser storage restrictions. Safari has not been tested. Older deployed code that does not acquire the lock cannot be fenced by this gate; close old tabs and refresh the SW/bundles when validating the new protocol.
Validation¶
Run unit tests and the scope E2E regression in Chromium and Firefox with fresh SW caches. Check that blocked tabs never touch persistence and closing/reloading the owner releases ownership. Existing plugin/reload and crash-recovery tests remain acceptance gates. Revisit only if concurrent shared-scope editing becomes required.
Follow-up work¶
Validate Safari's real iframe/page lifecycle before claiming support there.
References¶
src/shared/scope-ownership.jssrc/remote/main.js,src/shell/main.js,src/shared/protocol.jstests/shared/scope-ownership.test.js,tests/e2e/scope-ownership.spec.mjstests/runtime/worker-configuration.test.js