aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/downloads.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/downloads.js33
1 files changed, 32 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
index f620e15..cfb0051 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/downloads.js
@@ -216,6 +216,17 @@ const SW_CONTROL_BUDGET_MS = 15000;
const SW_SERVED_BUDGET_MS = 15000;
// A transient miss gets a second go with a fresh id and a fresh iframe.
const SW_ATTEMPTS = 2;
+// How often the page pokes the worker while a download is being written.
+// Firefox terminates a service worker that has had no event for roughly thirty
+// seconds, and a streaming response does not count as activity — so a download
+// that takes longer than that lost its reader half way through. Ten seconds
+// leaves a wide margin and costs one empty message.
+const SW_KEEPALIVE_MS = 10000;
+// And the ping stops on its own once nothing has been written for this long.
+// Well past any real gap between chunks, and short enough that an abandoned
+// target does not ping for ever. Bounded because the alternative is a timer
+// whose lifetime depends on every caller remembering to close its sink.
+const SW_KEEPALIVE_IDLE_MS = 120000;
// Holds a *successful* controller, or an in-flight attempt. Never a failure —
// see serviceWorker(). The previous version cached the rejected/null result
@@ -399,6 +410,24 @@ async function _attemptStreamedDownload(filename, size, attempt,
}
_lastFailure = '';
+ // Every few seconds for as long as this download is being written. Well
+ // inside the ~30 s Firefox allows an idle worker, and cheap: one postMessage
+ // with no payload. Cleared by close() and abort() below, so a finished
+ // download leaves no timer behind.
+ // Self-limiting, and that is not belt-and-braces: a target can be opened and
+ // then never written to — a transfer cancelled while it waits for a slot
+ // never runs, so nothing calls close() or abort() — and an interval nobody
+ // clears pings for the life of the page. It also kept the Node test process
+ // alive for ever, which is the same defect wearing a louder symptom (the
+ // MessagePort above did exactly this a few hours earlier).
+ let lastWrite = Date.now();
+ const keepAlive = setInterval(() => {
+ if (Date.now() - lastWrite > SW_KEEPALIVE_IDLE_MS) {
+ clearInterval(keepAlive);
+ return;
+ }
+ try { worker.postMessage({ type: 'mbdl-ping' }); } catch { /* gone */ }
+ }, SW_KEEPALIVE_MS);
// The port has delivered the one message it exists for. Closing it matters:
// an open MessagePort is a live handle, and one was leaked per download for
// the life of the page. (It is also what hung the Node harness in
@@ -410,12 +439,14 @@ async function _attemptStreamedDownload(filename, size, attempt,
return {
name: filename,
writable: {
- write: (bytes) => writer.write(bytes),
+ write: (bytes) => { lastWrite = Date.now(); return writer.write(bytes); },
close: async () => {
+ clearInterval(keepAlive);
await writer.close();
setTimeout(() => frame.remove(), 2000);
},
abort: async (reason) => {
+ clearInterval(keepAlive);
try { await writer.abort(reason); } catch { /* already gone */ }
frame.remove();
},