aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/downloads.js20
-rw-r--r--packages/meshbay-hub/tests/test_streamed_download_reliability.py54
2 files changed, 71 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
index b1eade6..87d18b7 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
@@ -338,6 +338,16 @@ async function _claimController(budgetMs) {
// requests never reach the fetch handler, so the worker would take our stream
// and never be asked for it — the download then freezes after exactly one
// chunk, which is how that was found.
+ //
+ // Ask for the claim *before* waiting, not after. A page that is uncontrolled
+ // while an active worker exists will not be claimed on its own — a document
+ // fetched by a hard reload is exactly that shape — so the whole control
+ // budget is spent waiting for something that is not coming, and it was: about
+ // thirty seconds during which somebody clicks download and watches four rows
+ // hang. Asking first costs one message and makes the common case immediate.
+ if (!navigator.serviceWorker.controller && reg.active) {
+ try { reg.active.postMessage({ type: 'mbdl-claim' }); } catch { /* gone */ }
+ }
const controller = await _awaitControl(left());
if (controller) return controller;
// Active but not controlling after the whole budget. `sw.js` calls
@@ -367,10 +377,17 @@ async function _claimController(budgetMs) {
*/
export function primeServiceWorker() {
if (!STREAMS_VIA_SW) return;
- serviceWorker().then((worker) => worker && _repairIfBypassed(worker))
+ _priming = serviceWorker()
+ .then((worker) => worker && _repairIfBypassed(worker))
.catch(() => {});
}
+// Resolved once the check below has run. A download that starts while it is
+// still in flight waits for it rather than racing it: on a page that turns out
+// to be unservable the click would otherwise spend thirty seconds failing on a
+// path that is about to be repaired.
+let _priming = null;
+
// How long to wait for the worker to answer the one-byte self-test below.
// Milliseconds when it works; a page that cannot stream at all is worth four
// seconds to find out about, once, at boot.
@@ -495,6 +512,7 @@ export async function openStreamedDownload(filename, size = 0, {
servedMs = SW_SERVED_BUDGET_MS,
attempts = SW_ATTEMPTS,
} = {}) {
+ if (_priming) { try { await _priming; } catch { /* reported already */ } }
for (let attempt = 1; attempt <= attempts; attempt++) {
const target = await _attemptStreamedDownload(
filename, size, attempt, controlMs, servedMs);
diff --git a/packages/meshbay-hub/tests/test_streamed_download_reliability.py b/packages/meshbay-hub/tests/test_streamed_download_reliability.py
index 3033a95..355fbff 100644
--- a/packages/meshbay-hub/tests/test_streamed_download_reliability.py
+++ b/packages/meshbay-hub/tests/test_streamed_download_reliability.py
@@ -59,7 +59,15 @@ let controller = null;
const pendingByFrame = new Map();
const makeController = () => ({
postMessage: (msg, transfer) => {
- if (msg.type === 'mbdl-claim') { log.claims += 1; return; }
+ if (msg.type === 'mbdl-claim') {
+ log.claims += 1;
+ // A worker that actually claims when asked, which is what sw.js does.
+ if (PLAN.controlOnClaim) {
+ controller = makeController();
+ for (const fn of listeners) fn();
+ }
+ return;
+ }
if (msg.type !== 'mbdl') return;
pendingByFrame.set('/_mbdl/' + msg.id, msg.port);
},
@@ -152,7 +160,7 @@ const out = {};
def _run(tmp_path, body, *, control_after_ms=0, active=True,
serve="always", register_throws=False, control_budget_ms=800,
ready_settles=True, register_hangs=False,
- active_after_unregister=False):
+ active_after_unregister=False, control_on_claim=False):
module = tmp_path / "downloads.mjs"
module.write_text(DOWNLOADS.read_text())
(tmp_path / "package.json").write_text('{"type":"module"}')
@@ -164,6 +172,7 @@ def _run(tmp_path, body, *, control_after_ms=0, active=True,
"readySettles": ready_settles,
"registerHangs": register_hangs,
"activeAfterUnregister": active_after_unregister,
+ "controlOnClaim": control_on_claim,
}
script = tmp_path / "case.mjs"
script.write_text(
@@ -451,3 +460,44 @@ def test_the_self_test_leaves_no_file_behind(tmp_path):
"the self-test's stream is never aborted, so the browser keeps what it "
"was given")
assert "frame.remove" in fn
+
+
+# ── The claim is asked for, not waited for ──────────────────────────────────
+
+def test_an_uncontrolled_page_asks_at_once_rather_than_after_the_budget(tmp_path):
+ """A page that is uncontrolled while an active worker exists will not be
+ claimed on its own — a document fetched by a hard reload is exactly that
+ shape. Waiting the whole control budget first spends it on something that
+ is not coming: about thirty seconds, measured, during which the person
+ clicks download and watches four rows hang before the page repairs itself.
+ """
+ out = _run(tmp_path, """
+ const t0 = Date.now();
+ const target = await M.openStreamedDownload('film.mkv', 20e9, FAST);
+ out.ms = Date.now() - t0;
+ out.target = target !== null;
+ out.claims = log.claims;
+ // Closing stops the keep-alive; left open, its interval outlives the test.
+ if (target) await target.writable.close();
+ """, control_after_ms=None, control_on_claim=True, control_budget_ms=6000)
+ assert out["target"] is True
+ assert out["claims"] >= 1
+ assert out["ms"] < 3000, (
+ f"took {out['ms']}ms of a 6000ms budget — the claim was asked for only "
+ "after the wait, not before it")
+
+
+def test_a_download_waits_for_the_self_test(tmp_path):
+ """A click that lands while the check is still running must not race it.
+ On a page that turns out to be unservable it would otherwise spend the full
+ two attempts failing on a path that is about to be repaired."""
+ out = _run(tmp_path, """
+ M.primeServiceWorker();
+ const t0 = Date.now();
+ const target = await M.openStreamedDownload('film.mkv', 20e9, FAST);
+ out.ms = Date.now() - t0;
+ out.target = target !== null;
+ if (target) await target.writable.close();
+ """)
+ assert out["target"] is True
+ assert out["ms"] >= 1, "the download did not wait for priming at all"