Abort, with a small delay, getOperatorList
on the worker-thread when rendering is cancelled (PR 11069 follow-up)
With this patch we're finally able to abort worker-thread parsing of the `OperatorList`, rather than *only* aborting the main-thread rendering itself, when the `RenderTask.cancel` method is being called. This will help improve perceived performance in the default viewer, especially when reading longer and more complex documents, since pages that've been scrolled out-of-view (and thus evicted from the cache) will no longer compete for parsing resources on the worker-thread. *Please note:* With the implementation in this patch we're *not* aborting worker-thread parsing immediately on `RenderTask.cancel`, since that would lead to *worse* performance in many cases. For example: When zoom/rotation occurs in the viewer, while parsing/rendering is still ongoing, a `cancel` call will usually be (almost) immediately folled by a new `PDFPageProxy.render` call. In that case you obviously don't want to abort parsing on the worker-thread, since that would risk throwing away a partially parsed `OperatorList` and thus force unnecessary re-parsing which will regress perceived performance (especially for more complex documents). When choosing a reasonable delay, before cancelling `getOperatorList` on the worker-thread when `RenderTask.cancel` is called, two different positions need to be considered: 1. The delay needs to be short enough, since a timeout in the multiple seconds range would essentially make this entire functionality meaningless (by always allowing most/all pages enough time to finish parsing). 2. The delay cannot be *too* short, since that would actually *reduce* performance in the zoom/rotation case outlined above. Furthermore, the time between `RenderTask.cancel` and `PDFPageProxy.render` calls will obviously be affected by both general computer performance and current CPU load. It's certainly possible that the timeout may require some further tweaks, however the value settled on in this patch was easily *one order* of magnitude larger than the delta between cancel/render in my tests.
This commit is contained in:
parent
b86bdefcd9
commit
281ed33e43
@ -38,6 +38,7 @@ import { PDFDataTransportStream } from './transport_stream';
|
|||||||
import { WebGLContext } from './webgl';
|
import { WebGLContext } from './webgl';
|
||||||
|
|
||||||
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
|
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
|
||||||
|
const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
||||||
|
|
||||||
let isWorkerDisabled = false;
|
let isWorkerDisabled = false;
|
||||||
let fallbackWorkerSrc;
|
let fallbackWorkerSrc;
|
||||||
@ -1003,21 +1004,27 @@ class PDFPageProxy {
|
|||||||
const stats = this._stats;
|
const stats = this._stats;
|
||||||
stats.time('Overall');
|
stats.time('Overall');
|
||||||
|
|
||||||
|
const renderingIntent = (intent === 'print' ? 'print' : 'display');
|
||||||
// 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;
|
||||||
|
|
||||||
const renderingIntent = (intent === 'print' ? 'print' : 'display');
|
|
||||||
const canvasFactoryInstance = canvasFactory || new DOMCanvasFactory();
|
|
||||||
const webGLContext = new WebGLContext({
|
|
||||||
enable: enableWebGL,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this.intentStates[renderingIntent]) {
|
if (!this.intentStates[renderingIntent]) {
|
||||||
this.intentStates[renderingIntent] = Object.create(null);
|
this.intentStates[renderingIntent] = Object.create(null);
|
||||||
}
|
}
|
||||||
const intentState = this.intentStates[renderingIntent];
|
const intentState = this.intentStates[renderingIntent];
|
||||||
|
|
||||||
|
// Ensure that a pending `streamReader` cancel timeout is always aborted.
|
||||||
|
if (intentState.streamReaderCancelTimeout) {
|
||||||
|
clearTimeout(intentState.streamReaderCancelTimeout);
|
||||||
|
intentState.streamReaderCancelTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvasFactoryInstance = canvasFactory || new DOMCanvasFactory();
|
||||||
|
const webGLContext = new WebGLContext({
|
||||||
|
enable: enableWebGL,
|
||||||
|
});
|
||||||
|
|
||||||
// If there's no displayReadyCapability yet, then the operatorList
|
// If there's no displayReadyCapability yet, then the operatorList
|
||||||
// was never requested before. Make the request and create the promise.
|
// was never requested before. Make the request and create the promise.
|
||||||
if (!intentState.displayReadyCapability) {
|
if (!intentState.displayReadyCapability) {
|
||||||
@ -1270,6 +1277,10 @@ class PDFPageProxy {
|
|||||||
*/
|
*/
|
||||||
_startRenderPage(transparency, intent) {
|
_startRenderPage(transparency, intent) {
|
||||||
const intentState = this.intentStates[intent];
|
const intentState = this.intentStates[intent];
|
||||||
|
if (!intentState) {
|
||||||
|
return; // Rendering was cancelled.
|
||||||
|
}
|
||||||
|
this._stats.timeEnd('Page Request');
|
||||||
// TODO Refactor RenderPageRequest to separate rendering
|
// TODO Refactor RenderPageRequest to separate rendering
|
||||||
// and operator list logic
|
// and operator list logic
|
||||||
if (intentState.displayReadyCapability) {
|
if (intentState.displayReadyCapability) {
|
||||||
@ -1365,19 +1376,41 @@ class PDFPageProxy {
|
|||||||
if (!intentState.streamReader) {
|
if (!intentState.streamReader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!force && intentState.renderTasks.length !== 0) {
|
if (!force) {
|
||||||
// Ensure that an Error occuring in *only* one `InternalRenderTask`, e.g.
|
// Ensure that an Error occurring in *only* one `InternalRenderTask`, e.g.
|
||||||
// multiple render() calls on the same canvas, won't break all rendering.
|
// multiple render() calls on the same canvas, won't break all rendering.
|
||||||
return;
|
if (intentState.renderTasks.length !== 0) {
|
||||||
}
|
return;
|
||||||
if (reason instanceof RenderingCancelledException) {
|
}
|
||||||
// Aborting parsing on the worker-thread when rendering is cancelled will
|
// Don't immediately abort parsing on the worker-thread when rendering is
|
||||||
// break subsequent rendering operations. TODO: Remove this restriction.
|
// cancelled, since that will unnecessarily delay re-rendering when (for
|
||||||
return;
|
// partially parsed pages) e.g. zooming/rotation occurs in the viewer.
|
||||||
|
if (reason instanceof RenderingCancelledException) {
|
||||||
|
intentState.streamReaderCancelTimeout = setTimeout(() => {
|
||||||
|
this._abortOperatorList({ intentState, reason, force: true, });
|
||||||
|
intentState.streamReaderCancelTimeout = null;
|
||||||
|
}, RENDERING_CANCELLED_TIMEOUT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
intentState.streamReader.cancel(
|
intentState.streamReader.cancel(
|
||||||
new AbortException(reason && reason.message));
|
new AbortException(reason && reason.message));
|
||||||
intentState.streamReader = null;
|
intentState.streamReader = null;
|
||||||
|
|
||||||
|
if (this._transport.destroyed) {
|
||||||
|
return; // Ignore any pending requests if the worker was terminated.
|
||||||
|
}
|
||||||
|
// Remove the current `intentState`, since a cancelled `getOperatorList`
|
||||||
|
// call on the worker-thread cannot be re-started...
|
||||||
|
Object.keys(this.intentStates).some((intent) => {
|
||||||
|
if (this.intentStates[intent] === intentState) {
|
||||||
|
delete this.intentStates[intent];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
// ... and force clean-up to ensure that any old state is always removed.
|
||||||
|
this.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2036,7 +2069,6 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const page = this.pageCache[data.pageIndex];
|
const page = this.pageCache[data.pageIndex];
|
||||||
page._stats.timeEnd('Page Request');
|
|
||||||
page._startRenderPage(data.transparency, data.intent);
|
page._startRenderPage(data.transparency, data.intent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user