Skip to content

Commit cffb19e

Browse files
committed
Send 'Terminate' to the worker when destroy races the load-time handshake (bug 1942304)
If `PDFDocumentLoadingTask.destroy` ran while `workerIdPromise` was pending, the inner `.then` in `getDocument` threw "Loading aborted" before `WorkerTransport` was constructed, so `_transport` was never set and the "Terminate" message was never posted.
1 parent 8d3d370 commit cffb19e

1 file changed

Lines changed: 31 additions & 7 deletions

File tree

src/display/api.js

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -424,9 +424,6 @@ function getDocument(src = {}) {
424424

425425
Promise.all([worker.promise, gpuPromise])
426426
.then(function ([, hasGPU]) {
427-
if (task.destroyed) {
428-
throw new Error("Loading aborted");
429-
}
430427
if (worker.destroyed) {
431428
throw new Error("Worker was destroyed");
432429
}
@@ -469,9 +466,6 @@ function getDocument(src = {}) {
469466
}
470467

471468
return workerIdPromise.then(workerId => {
472-
if (task.destroyed) {
473-
throw new Error("Loading aborted");
474-
}
475469
if (worker.destroyed) {
476470
throw new Error("Worker was destroyed");
477471
}
@@ -486,10 +480,18 @@ function getDocument(src = {}) {
486480
pagesMapper
487481
);
488482
task._transport = transport;
483+
484+
if (task.destroyed) {
485+
// `destroy()` was called during the worker handshake; the orderly
486+
// shutdown (including the "Terminate" message) will be issued
487+
// through the transport once destroy resumes.
488+
throw new Error("Loading aborted");
489+
}
489490
messageHandler.send("Ready", null);
490491
});
491492
})
492-
.catch(task._capability.reject);
493+
.catch(task._capability.reject)
494+
.finally(task._setupCapability.resolve);
493495

494496
return task;
495497
}
@@ -515,6 +517,14 @@ class PDFDocumentLoadingTask {
515517
*/
516518
_capability = Promise.withResolvers();
517519

520+
/**
521+
* Resolves once the load-time setup chain has settled, regardless of
522+
* outcome; used by `destroy()` to wait until `_transport` is either set
523+
* or definitely never going to be.
524+
* @private
525+
*/
526+
_setupCapability = Promise.withResolvers();
527+
518528
/**
519529
* @private
520530
*/
@@ -568,11 +578,25 @@ class PDFDocumentLoadingTask {
568578
*/
569579
async destroy() {
570580
this.destroyed = true;
581+
// The setup chain rejects `_capability` with "Loading aborted" once the
582+
// load-time chain unwinds (see `getDocument`). Claim that rejection
583+
// here so it isn't reported as unhandled during the awaits below;
584+
// callers awaiting `task.promise` still see it.
585+
this._capability.promise.catch(() => {});
571586

572587
try {
588+
// `_pendingDestroy` must be set synchronously, before any `await`,
589+
// so subsequent `PDFWorker.create()` calls on the shared `workerPort`
590+
// observe it and throw (see issue 16777).
573591
if (this._worker?.port) {
574592
this._worker._pendingDestroy = true;
575593
}
594+
// Wait for the load-time setup chain to settle so `_transport` is set
595+
// (when applicable) before we tear down. This is what guarantees the
596+
// "Terminate" message gets sent through `WorkerTransport.destroy` if
597+
// `destroy` races with the initial worker handshake.
598+
await this._setupCapability.promise;
599+
576600
await this._transport?.destroy();
577601
} catch (ex) {
578602
if (this._worker?.port) {

0 commit comments

Comments
 (0)