Preserve telemetry during aborted worker cleanup

This commit is contained in:
Stefan Stidl 2026-09-12 13:06:42 +02:00
parent ef3ac7f373
commit 9f8826fb76
3 changed files with 55 additions and 9 deletions

View file

@ -336,6 +336,13 @@ Speedtest.prototype = {
const isCurrentRun = function() {
return this._activeRun === run && this.worker === worker;
}.bind(this);
const armAbortTimeout = function() {
if (run.abortTimeout !== null) clearTimeout(run.abortTimeout);
run.abortTimeout = setTimeout(function() {
if (this._activeRun === run && this.worker === run.worker)
run.finish(true);
}.bind(this), 1000);
}.bind(this);
const finish = function(aborted) {
if (run.ended) return;
run.ended = true;
@ -362,14 +369,18 @@ Speedtest.prototype = {
}
}.bind(this);
run.armAbortTimeout = armAbortTimeout;
run.finish = finish;
this._activeRun = run;
this.worker = worker;
worker.onmessage = function(e) {
if (!isCurrentRun() || run.ended) return;
const data = JSON.parse(e.data);
// While an aborted run is submitting telemetry, each status response
// proves that the worker is still responsive, so keep its watchdog alive.
if (data.abortPending && run.abortTimeout !== null) armAbortTimeout();
if (e.data === run.prevData) return;
else run.prevData = e.data;
const data = JSON.parse(e.data);
try {
if (this.onupdate) this.onupdate(data);
} catch (e) {
@ -415,10 +426,7 @@ Speedtest.prototype = {
if (this._state < 4 && run && this.worker === run.worker) {
run.worker.postMessage("abort");
if (!run.ended && run.abortTimeout === null) {
run.abortTimeout = setTimeout(function() {
if (this._activeRun === run && this.worker === run.worker)
run.finish(true);
}.bind(this), 1000);
run.armAbortTimeout();
}
}
}

View file

@ -16,6 +16,7 @@ let dlProgress = 0; //progress of download test 0-1
let ulProgress = 0; //progress of upload test 0-1
let pingProgress = 0; //progress of ping+jitter test 0-1
let testId = null; //test ID (sent back by telemetry if used, null otherwise)
let abortPending = false; // true while aborted-run telemetry is being submitted
let log = ""; //telemetry log
function tlog(s) {
@ -102,7 +103,8 @@ this.addEventListener("message", function(e) {
dlProgress: dlProgress,
ulProgress: ulProgress,
pingProgress: pingProgress,
testId: testId
testId: testId,
abortPending: abortPending
})
);
}
@ -246,13 +248,12 @@ this.addEventListener("message", function(e) {
}
if (params[0] === "abort") {
// abort command
if (testState >= 4) return;
if (testState >= 4 || abortPending) return;
tlog("manually aborted");
clearRequests(); // stop all xhr activity
runNextTest = null;
if (interval) clearInterval(interval); // clear timer if present
if (settings.telemetry_level > 1) sendTelemetry(function() {});
testState = 5; //set test as aborted
abortPending = settings.telemetry_level > 1;
dlStatus = "";
ulStatus = "";
pingStatus = "";
@ -261,6 +262,12 @@ this.addEventListener("message", function(e) {
dlProgress = 0;
ulProgress = 0;
pingProgress = 0;
const completeAbort = function() {
abortPending = false;
testState = 5; //set test as aborted
};
if (abortPending) sendTelemetry(completeAbort);
else completeAbort();
}
});
// stops all XHR activity, aggressively

View file

@ -182,6 +182,37 @@ test.describe("Speedtest worker lifecycle", () => {
expect(await page.evaluate(() => window.__endCalls)).toBe(1);
});
test("keeps a responsive worker alive while aborted-run telemetry is pending", async ({ page }) => {
await page.evaluate(() => {
window.__speedtest = new Speedtest();
window.__speedtest.start();
window.__speedtest.abort();
window.__worker = window.__workers[0];
window.__telemetryStatus = setInterval(
() => window.__worker.emit({ testState: 3, abortPending: true }),
100
);
});
await page.waitForTimeout(1100);
await page.evaluate(() => clearInterval(window.__telemetryStatus));
expect(
await page.evaluate(() => ({
state: window.__speedtest.getState(),
terminated: window.__worker.terminated
}))
).toEqual({ state: 3, terminated: false });
const result = await page.evaluate((data) => {
window.__worker.emit(data);
return {
state: window.__speedtest.getState(),
terminated: window.__worker.terminated
};
}, terminalState(5));
expect(result).toEqual({ state: 4, terminated: true });
});
test("ignores delayed events and an old abort timeout after a new run starts", async ({ page }) => {
await page.evaluate((data) => {
window.__speedtest = new Speedtest();