Skip to content

Fix Worker was terminated error when loading is cancelled#20503

Merged
calixteman merged 1 commit intomozilla:masterfrom
andriivitiv:Fix-`Worker-was-terminated`-error
Feb 6, 2026
Merged

Fix Worker was terminated error when loading is cancelled#20503
calixteman merged 1 commit intomozilla:masterfrom
andriivitiv:Fix-`Worker-was-terminated`-error

Conversation

@andriivitiv
Copy link
Contributor

Fixes #11595, where cancelling loading with loadingTask.destroy() before it finishes throws a Worker was terminated error that CANNOT be caught.

When worker is terminated, an error is thrown here:

throw new Error("Worker was terminated");

Then onFailure runs, in which we throw again via ensureNotTerminated(). However, this second error is never caught (and cannot be), resulting in console spam.

There is no need to throw any additional errors since the termination is already reported here, and onFailure is supposed to handle errors, not throw them.

Fixes mozilla#11595, where cancelling loading with `loadingTask.destroy()` before it finishes throws a `Worker was terminated` error that CANNOT be caught.

When worker is terminated, an error is thrown here:

https://github.com/mozilla/pdf.js/blob/6c746260a98766b8ece27018d2c48436cfcafa24/src/core/worker.js#L374

Then `onFailure` runs, in which we throw again via `ensureNotTerminated()`. However, this second error is never caught (and cannot be), resulting in console spam.

There is no need to throw any additional errors since the termination is already reported [here](https://github.com/mozilla/pdf.js/blob/6c746260a98766b8ece27018d2c48436cfcafa24/src/core/worker.js#L371-L373), and `onFailure` is supposed to handle errors, not throw them.
@calixteman
Copy link
Contributor

Is it possible to write a test ?

@andriivitiv
Copy link
Contributor Author

Is it possible to write a test ?

@calixteman I think the closest way would be something like the following:

Code
function makeAsyncCallback() {
  let resolve;

  const promise = new Promise(r => {
    resolve = r;
  });

  const func = function () {
    resolve();
  };

  return { func, promise };
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function onUnhandledRejection(event) {
  fail(event.reason || event);
}



it("no unhandled error is thrown when loading is cancelled", async function () {
  if (isNodeJS) {
    pending("Worker is not supported in Node.js.");
  }

  const { func: onProgress, promise: waitForProgress } = makeAsyncCallback();

  window.addEventListener("unhandledrejection", onUnhandledRejection);

  try {
    const loadingTask = getDocument(basicApiGetDocumentParams);
    loadingTask.onProgress = onProgress;

    await waitForProgress;
    await loadingTask.destroy();

    // There's probably a better way to wait a bit.
    await sleep(1000);
  } finally {
    window.removeEventListener("unhandledrejection", onUnhandledRejection);
  }
});

However, it still doesn’t work because triggering this error requires very specific timing. For example, I created a simple reproduction by modifying this file, and I can only get this error when throttling is enabled (probably because the PDF is too small). So i think it won't be possible to write a reliable test.

Code
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>'Hello, world!' example</title>
  </head>
  <body>
    <h1>'Hello, world!' example</h1>

    <button id="reload">Reload</button>
    <br /><br />

    <canvas id="the-canvas" style="border: 1px solid black; direction: ltr"></canvas>

    <script src="../../build/generic/build/pdf.mjs" type="module"></script>

    <script id="script" type="module">
      pdfjsLib.GlobalWorkerOptions.workerSrc = "../../build/generic/build/pdf.worker.mjs";

      const url = "./helloworld.pdf";

      const canvas = document.getElementById("the-canvas");
      const ctx = canvas.getContext("2d");

      let loadingTask = null;

      async function loadDocument() {
        if (loadingTask) {
          await loadingTask.destroy();
          loadingTask = null;
        }

        loadingTask = pdfjsLib.getDocument({url});

        try {
          const pdf = await loadingTask.promise;
          const page = await pdf.getPage(1);

          const scale = 1.5;
          const viewport = page.getViewport({ scale });
          const dpr = window.devicePixelRatio || 1;

          canvas.width = viewport.width * dpr;
          canvas.height = viewport.height * dpr;
          canvas.style.width = viewport.width + "px";
          canvas.style.height = viewport.height + "px";

          const transform = dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : null;

          page.render({
            canvasContext: ctx,
            viewport,
            transform,
          }).promise;
        } catch (err) {
            if (loadingTask?.destroyed) return;
            console.error(err);
        }
      }

      // initial load
      loadDocument();

      document.getElementById("reload").onclick = loadDocument;
    </script>

    <hr />
    <h2>JavaScript code:</h2>
    <pre id="code"></pre>
    <script>
      document.getElementById("code").textContent =
        document.getElementById("script").text;
    </script>
  </body>
</html>
Screen.Recording.2026-01-09.at.21.35.16.mov

@calixteman
Copy link
Contributor

I think it should be good.
@timvandermeij wdyt yourself ?

@andriivitiv
Copy link
Contributor Author

@timvandermeij Any thoughts on this PR?

Copy link
Contributor

@timvandermeij timvandermeij left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't foresee unwanted side-effects of doing this and can follow your reasoning, so this looks good to me assuming all tests pass.

@timvandermeij
Copy link
Contributor

/botio test

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Windows)


Received

Command cmd_test from @timvandermeij received. Current queue size: 0

Live output at: http://54.193.163.58:8877/d64f89e45248d85/output.txt

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Linux m4)


Received

Command cmd_test from @timvandermeij received. Current queue size: 0

Live output at: http://54.241.84.105:8877/0a702a2ae504a94/output.txt

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Linux m4)


Failed

Full output at http://54.241.84.105:8877/0a702a2ae504a94/output.txt

Total script time: 0.28 mins

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Windows)


Failed

Full output at http://54.193.163.58:8877/d64f89e45248d85/output.txt

Total script time: 83.86 mins

  • Unit tests: FAILED
  • Integration Tests: FAILED
  • Regression tests: FAILED
  different ref/snapshot: 1

Image differences available at: http://54.193.163.58:8877/d64f89e45248d85/reftest-analyzer.html#web=eq.log

@calixteman
Copy link
Contributor

/botio-linux test

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Linux m4)


Received

Command cmd_test from @calixteman received. Current queue size: 0

Live output at: http://54.241.84.105:8877/0057f4a07e910df/output.txt

@moz-tools-bot
Copy link
Collaborator

From: Bot.io (Linux m4)


Failed

Full output at http://54.241.84.105:8877/0057f4a07e910df/output.txt

Total script time: 0.36 mins

@calixteman
Copy link
Contributor

The bot is unhappy with the branch name.
The tests are ok on Windows so it should be fine on Linux too.

@calixteman calixteman merged commit 58ac273 into mozilla:master Feb 6, 2026
9 checks passed
@andriivitiv andriivitiv deleted the Fix-`Worker-was-terminated`-error branch February 8, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

loadingTask.destroy() throws worker exception

4 participants