From 3217490d8157c04959cb9d94656e8180aaf65c7b Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:03:54 +0100 Subject: [PATCH] Fix read worker abort bugs (fixes #197) --- src/source.ts | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/source.ts b/src/source.ts index 2fe74d4..adafa2e 100644 --- a/src/source.ts +++ b/src/source.ts @@ -234,11 +234,19 @@ export class BlobSource extends Source { break; } + if (worker.aborted) { + break; + } + this.onread?.(worker.currentPos, worker.currentPos + value.length); this._orchestrator.supplyWorkerData(worker, value); } else { const data = await this._blob.slice(worker.currentPos, worker.targetPos).arrayBuffer(); + if (worker.aborted) { + break; + } + this.onread?.(worker.currentPos, worker.currentPos + data.byteLength); this._orchestrator.supplyWorkerData(worker, new Uint8Array(data)); } @@ -457,7 +465,7 @@ export class UrlSource extends Source { /** @internal */ private async _runWorker(worker: ReadWorker) { // The outer loop is for resuming a request if it dies mid-response - while (!worker.aborted) { + while (true) { const existing = this._existingResponses.get(worker); this._existingResponses.delete(worker); @@ -534,6 +542,10 @@ export class UrlSource extends Source { } } + if (worker.aborted) { + break; + } + const { done, value } = readResult; if (done) { @@ -552,6 +564,10 @@ export class UrlSource extends Source { this.onread?.(worker.currentPos, worker.currentPos + value.length); this._orchestrator.supplyWorkerData(worker, value); } + + if (worker.aborted) { + break; + } } worker.running = false; @@ -796,6 +812,10 @@ export class StreamSource extends Source { let data = this._options.read(worker.currentPos, originalTargetPos); if (data instanceof Promise) data = await data; + if (worker.aborted) { + break; + } + if (data instanceof Uint8Array) { data = toUint8Array(data); // Normalize things like Node.js Buffer to Uint8Array @@ -833,6 +853,10 @@ export class StreamSource extends Source { throw new TypeError('ReadableStream returned by options.read must yield Uint8Array chunks.'); } + if (worker.aborted) { + break; + } + const data = toUint8Array(value); // Normalize things like Node.js Buffer to Uint8Array this.onread?.(worker.currentPos, worker.currentPos + data.length); @@ -1420,7 +1444,10 @@ class ReadOrchestrator { currentPos: startPos, targetPos, running: false, - aborted: false, + // Due to async shenanigans, it can happen that workers are started after disposal. In this case, instead of + // simply not creating the worker, we allow it to run but immediately label it as aborted, so it can then + // shut itself down. + aborted: this.disposed, pendingSlices: [], age: this.nextAge++, }; @@ -1473,10 +1500,7 @@ class ReadOrchestrator { /** Called by a worker when it has read some data. */ supplyWorkerData(worker: ReadWorker, bytes: Uint8Array) { - if (this.disposed) { - // Writes may still come in after disposal, but we just ignore those - return; - } + assert(!worker.aborted); const start = worker.currentPos; const end = start + bytes.length;