Merge pull request #16108 from Snuffleupagus/delay-cleanup

Slightly delay cleanup, after rendering, in documents with large images
This commit is contained in:
Tim van der Meij 2023-03-11 15:52:12 +01:00 committed by GitHub
commit 9819f1cc6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 27 deletions

View File

@ -13,7 +13,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert, shadow, unreachable, warn } from "../shared/util.js"; import {
assert,
MAX_IMAGE_SIZE_TO_CACHE,
shadow,
unreachable,
warn,
} from "../shared/util.js";
import { RefSetCache } from "./primitives.js"; import { RefSetCache } from "./primitives.js";
class BaseLocalCache { class BaseLocalCache {
@ -160,7 +166,7 @@ class GlobalImageCache {
} }
static get MAX_BYTE_SIZE() { static get MAX_BYTE_SIZE() {
return shadow(this, "MAX_BYTE_SIZE", /* Forty megabytes = */ 40e6); return shadow(this, "MAX_BYTE_SIZE", 5 * MAX_IMAGE_SIZE_TO_CACHE);
} }
constructor() { constructor() {

View File

@ -26,6 +26,7 @@ import {
info, info,
InvalidPDFException, InvalidPDFException,
isArrayBuffer, isArrayBuffer,
MAX_IMAGE_SIZE_TO_CACHE,
MissingPDFException, MissingPDFException,
PasswordException, PasswordException,
RenderingIntentFlag, RenderingIntentFlag,
@ -66,6 +67,7 @@ import { XfaText } from "./xfa_text.js";
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536 const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
const RENDERING_CANCELLED_TIMEOUT = 100; // ms const RENDERING_CANCELLED_TIMEOUT = 100; // ms
const DELAYED_CLEANUP_TIMEOUT = 5000; // ms
let DefaultCanvasFactory = DOMCanvasFactory; let DefaultCanvasFactory = DOMCanvasFactory;
let DefaultCMapReaderFactory = DOMCMapReaderFactory; let DefaultCMapReaderFactory = DOMCMapReaderFactory;
@ -1262,6 +1264,10 @@ class PDFDocumentProxy {
* Proxy to a `PDFPage` in the worker thread. * Proxy to a `PDFPage` in the worker thread.
*/ */
class PDFPageProxy { class PDFPageProxy {
#delayedCleanupTimeout = null;
#pendingCleanup = false;
constructor(pageIndex, pageInfo, transport, pdfBug = false) { constructor(pageIndex, pageInfo, transport, pdfBug = false) {
this._pageIndex = pageIndex; this._pageIndex = pageIndex;
this._pageInfo = pageInfo; this._pageInfo = pageInfo;
@ -1272,8 +1278,7 @@ class PDFPageProxy {
this.commonObjs = transport.commonObjs; this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects(); this.objs = new PDFObjects();
this.cleanupAfterRender = false; this._maybeCleanupAfterRender = false;
this.pendingCleanup = false;
this._intentStates = new Map(); this._intentStates = new Map();
this.destroyed = false; this.destroyed = false;
} }
@ -1413,8 +1418,10 @@ class PDFPageProxy {
printAnnotationStorage printAnnotationStorage
); );
// If there was a pending destroy, cancel it so no cleanup happens during // If there was a pending destroy, cancel it so no cleanup happens during
// this call to render. // this call to render...
this.pendingCleanup = false; this.#pendingCleanup = false;
// ... and ensure that a delayed cleanup is always aborted.
this.#abortDelayedCleanup();
if (!optionalContentConfigPromise) { if (!optionalContentConfigPromise) {
optionalContentConfigPromise = this._transport.getOptionalContentConfig(); optionalContentConfigPromise = this._transport.getOptionalContentConfig();
@ -1455,11 +1462,11 @@ class PDFPageProxy {
intentState.renderTasks.delete(internalRenderTask); intentState.renderTasks.delete(internalRenderTask);
// Attempt to reduce memory usage during *printing*, by always running // Attempt to reduce memory usage during *printing*, by always running
// cleanup once rendering has finished (regardless of cleanupAfterRender). // cleanup immediately once rendering has finished.
if (this.cleanupAfterRender || intentPrint) { if (this._maybeCleanupAfterRender || intentPrint) {
this.pendingCleanup = true; this.#pendingCleanup = true;
} }
this._tryCleanup(); this.#tryCleanup(/* delayed = */ !intentPrint);
if (error) { if (error) {
internalRenderTask.capability.reject(error); internalRenderTask.capability.reject(error);
@ -1509,7 +1516,7 @@ class PDFPageProxy {
{ transparency, isOffscreenCanvasSupported }, { transparency, isOffscreenCanvasSupported },
optionalContentConfig, optionalContentConfig,
]) => { ]) => {
if (this.pendingCleanup) { if (this.#pendingCleanup) {
complete(); complete();
return; return;
} }
@ -1681,7 +1688,9 @@ class PDFPageProxy {
} }
} }
this.objs.clear(); this.objs.clear();
this.pendingCleanup = false; this.#pendingCleanup = false;
this.#abortDelayedCleanup();
return Promise.all(waitOn); return Promise.all(waitOn);
} }
@ -1693,16 +1702,34 @@ class PDFPageProxy {
* @returns {boolean} Indicates if clean-up was successfully run. * @returns {boolean} Indicates if clean-up was successfully run.
*/ */
cleanup(resetStats = false) { cleanup(resetStats = false) {
this.pendingCleanup = true; this.#pendingCleanup = true;
return this._tryCleanup(resetStats); const success = this.#tryCleanup(/* delayed = */ false);
if (resetStats && success) {
this._stats &&= new StatTimer();
}
return success;
} }
/** /**
* Attempts to clean up if rendering is in a state where that's possible. * Attempts to clean up if rendering is in a state where that's possible.
* @private * @param {boolean} [delayed] - Delay the cleanup, to e.g. improve zooming
* performance in documents with large images.
* The default value is `false`.
* @returns {boolean} Indicates if clean-up was successfully run.
*/ */
_tryCleanup(resetStats = false) { #tryCleanup(delayed = false) {
if (!this.pendingCleanup) { this.#abortDelayedCleanup();
if (!this.#pendingCleanup) {
return false;
}
if (delayed) {
this.#delayedCleanupTimeout = setTimeout(() => {
this.#delayedCleanupTimeout = null;
this.#tryCleanup(/* delayed = */ false);
}, DELAYED_CLEANUP_TIMEOUT);
return false; return false;
} }
for (const { renderTasks, operatorList } of this._intentStates.values()) { for (const { renderTasks, operatorList } of this._intentStates.values()) {
@ -1710,16 +1737,19 @@ class PDFPageProxy {
return false; return false;
} }
} }
this._intentStates.clear(); this._intentStates.clear();
this.objs.clear(); this.objs.clear();
if (resetStats && this._stats) { this.#pendingCleanup = false;
this._stats = new StatTimer();
}
this.pendingCleanup = false;
return true; return true;
} }
#abortDelayedCleanup() {
if (this.#delayedCleanupTimeout) {
clearTimeout(this.#delayedCleanupTimeout);
this.#delayedCleanupTimeout = null;
}
}
/** /**
* @private * @private
*/ */
@ -1756,7 +1786,7 @@ class PDFPageProxy {
} }
if (operatorListChunk.lastChunk) { if (operatorListChunk.lastChunk) {
this._tryCleanup(); this.#tryCleanup(/* delayed = */ true);
} }
} }
@ -1814,7 +1844,7 @@ class PDFPageProxy {
for (const internalRenderTask of intentState.renderTasks) { for (const internalRenderTask of intentState.renderTasks) {
internalRenderTask.operatorListChanged(); internalRenderTask.operatorListChanged();
} }
this._tryCleanup(); this.#tryCleanup(/* delayed = */ true);
} }
if (intentState.displayReadyCapability) { if (intentState.displayReadyCapability) {
@ -2782,7 +2812,6 @@ class WorkerTransport {
pageProxy.objs.resolve(id, imageData); pageProxy.objs.resolve(id, imageData);
// Heuristic that will allow us not to store large data. // Heuristic that will allow us not to store large data.
const MAX_IMAGE_SIZE_TO_STORE = 8000000;
if (imageData) { if (imageData) {
let length; let length;
if (imageData.bitmap) { if (imageData.bitmap) {
@ -2792,8 +2821,8 @@ class WorkerTransport {
length = imageData.data?.length || 0; length = imageData.data?.length || 0;
} }
if (length > MAX_IMAGE_SIZE_TO_STORE) { if (length > MAX_IMAGE_SIZE_TO_CACHE) {
pageProxy.cleanupAfterRender = true; pageProxy._maybeCleanupAfterRender = true;
} }
} }
break; break;

View File

@ -26,6 +26,8 @@ if (
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const MAX_IMAGE_SIZE_TO_CACHE = 10e6; // Ten megabytes.
// Represent the percentage of the height of a single-line field over // Represent the percentage of the height of a single-line field over
// the font size. Acrobat seems to use this value. // the font size. Acrobat seems to use this value.
const LINE_FACTOR = 1.35; const LINE_FACTOR = 1.35;
@ -1060,6 +1062,7 @@ export {
isArrayEqual, isArrayEqual,
LINE_DESCENT_FACTOR, LINE_DESCENT_FACTOR,
LINE_FACTOR, LINE_FACTOR,
MAX_IMAGE_SIZE_TO_CACHE,
MissingPDFException, MissingPDFException,
objectFromMap, objectFromMap,
objectSize, objectSize,