Repository navigation
Description
Attach (recommended) or Link to PDF file
network_error.pdf, network_error_2.pdf
These come from real production incidents. The original PDFs are confidential, so these are anonymized copies (all content replaced, structure kept). The PDFs themselves are fine. The bug is triggered by the connection dropping while one of them is loading.
Web browser and its version
Chrome 156.0.8078.47
Operating system and its version
macOS 27.0
PDF.js version
v6.4.299 (latest release), and master at 24a63a4 (also the online demo, v6.5.410)
Is the bug present in the latest PDF.js version?
Yes
Is a browser extension
No
Steps to reproduce the problem
- Save the script below as
repro.mjs. It serves the PDF, drops the connection halfway through the response body, and loads it with the released pdf.js from jsDelivr. node repro.mjs drop network_error.pdf- Open http://localhost:8000/ and the console.
// Usage: node repro.mjs <mode> <file.pdf>, then open http://localhost:8000/
// Modes: whole-file (range requests not starting at 0 get a 200 with the whole file)
// other-range (range requests are answered with a 206 for the preceding range)
// drop (the connection drops halfway through the response body)
import fs from "node:fs";
import http from "node:http";
const [mode, file] = process.argv.slice(2);
const data = fs.readFileSync(file);
const PDFJS = "https://cdn.jsdelivr.net/npm/pdfjs-dist@6.4.299/build";
const page = `<!doctype html><pre id="log"></pre><script type="module">
const log = msg => { document.getElementById("log").textContent += msg + "\\n"; console.log(msg); };
addEventListener("unhandledrejection", e => log("Uncaught (in promise): " + e.reason));
const pdfjsLib = await import("${PDFJS}/pdf.min.mjs");
pdfjsLib.GlobalWorkerOptions.workerSrc = "${PDFJS}/pdf.worker.min.mjs";
const task = pdfjsLib.getDocument({ url: "/file.pdf", disableAutoFetch: true, disableStream: true });
try {
const doc = await task.promise;
for (let i = 1; i <= doc.numPages; i++) await (await doc.getPage(i)).getOperatorList();
log("Loaded " + doc.numPages + " pages");
} catch (e) {
log("Failed: " + e);
}
await task.destroy();
setTimeout(() => log("done"), 500);
</script>`;
http.createServer((req, res) => {
if (req.url !== "/file.pdf") {
res.writeHead(200, { "Content-Type": "text/html" }).end(page);
return;
}
const headers = { "Content-Type": "application/pdf", "Accept-Ranges": "bytes" };
const range = /bytes=(\d+)-(\d+)/.exec(req.headers.range ?? "");
if (mode === "drop") {
res.writeHead(200, { ...headers, "Content-Length": data.length });
res.write(data.subarray(0, data.length >> 1), () => res.destroy());
} else if (!range || (mode === "whole-file" && range[1] !== "0")) {
res.writeHead(200, { ...headers, "Content-Length": data.length }).end(data);
} else {
const [start, end] = [+range[1], +range[2] + 1];
// other-range: the same length of data, but from the preceding range
const begin = mode === "other-range" ? Math.max(0, 2 * start - end) : start;
res.writeHead(206, {
...headers,
"Content-Range": `bytes ${begin}-${begin + end - start - 1}/${data.length}`,
"Content-Length": end - start,
});
res.end(data.subarray(begin, begin + end - start));
}
}).listen(8000, () => console.log(`${mode}: open http://localhost:8000/`));What is the expected behavior?
getDocument rejects with the network error, and destroying the loading task afterwards (e.g. the viewer is closed) is silent.
What went wrong?
After the document fails to load, loadingTask.destroy() cancels the errored stream reader. ReadableStreamDefaultReader.cancel() then returns a promise rejected with the stream's error, which nothing handles:
Failed: UnknownErrorException: network error
Uncaught (in promise): TypeError: network error
We hit this in production twice, from users whose connection dropped while a PDF was loading. It was reported as an unhandled rejection from PDFFetchStreamReader.cancel.
JavaScript console:
Failed to load resource: net::ERR_CONTENT_LENGTH_MISMATCH
Uncaught (in promise) TypeError: network error
Additional context
The same this._reader.cancel(reason) without a .catch is in PDFFetchStreamReader, PDFFetchStreamRangeReader, the PDFWorkerStream readers, and the PDFNodeStream readers. Similar to #22171. I have a fix with unit tests that fail on master and pass with it. The fix is in #22371.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity