Adds thread abort capabilities.

This commit is contained in:
Yury Delendik 2015-10-20 20:50:32 -05:00
parent 59c13b32aa
commit 58c3ea0820
6 changed files with 186 additions and 53 deletions

View File

@ -281,7 +281,7 @@ var Annotation = (function AnnotationClosure() {
}.bind(this));
},
getOperatorList: function Annotation_getOperatorList(evaluator) {
getOperatorList: function Annotation_getOperatorList(evaluator, task) {
if (!this.appearance) {
return Promise.resolve(new OperatorList());
@ -308,7 +308,8 @@ var Annotation = (function AnnotationClosure() {
return resourcesPromise.then(function(resources) {
var opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
return evaluator.getOperatorList(self.appearance, resources, opList).
return evaluator.getOperatorList(self.appearance, task,
resources, opList).
then(function () {
opList.addOp(OPS.endAnnotation, []);
self.appearance.reset();
@ -319,7 +320,7 @@ var Annotation = (function AnnotationClosure() {
};
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
annotations, opList, pdfManager, partialEvaluator, intent) {
annotations, opList, pdfManager, partialEvaluator, task, intent) {
function reject(e) {
annotationsReadyCapability.reject(e);
@ -332,7 +333,7 @@ var Annotation = (function AnnotationClosure() {
if (intent === 'display' && annotations[i].isViewable() ||
intent === 'print' && annotations[i].isPrintable()) {
annotationPromises.push(
annotations[i].getOperatorList(partialEvaluator));
annotations[i].getOperatorList(partialEvaluator, task));
}
}
Promise.all(annotationPromises).then(function(datas) {
@ -564,9 +565,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
}
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator,
task) {
if (this.appearance) {
return Annotation.prototype.getOperatorList.call(this, evaluator);
return Annotation.prototype.getOperatorList.call(this, evaluator, task);
}
var opList = new OperatorList();
@ -579,7 +581,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
}
var stream = new Stream(stringToBytes(data.defaultAppearance));
return evaluator.getOperatorList(stream, this.fieldResources, opList).
return evaluator.getOperatorList(stream, task,
this.fieldResources, opList).
then(function () {
return opList;
});

View File

@ -161,7 +161,7 @@ var Page = (function PageClosure() {
}.bind(this));
},
getOperatorList: function Page_getOperatorList(handler, intent) {
getOperatorList: function Page_getOperatorList(handler, task, intent) {
var self = this;
var pdfManager = this.pdfManager;
@ -194,8 +194,8 @@ var Page = (function PageClosure() {
pageIndex: self.pageIndex,
intent: intent
});
return partialEvaluator.getOperatorList(contentStream, self.resources,
opList).then(function () {
return partialEvaluator.getOperatorList(contentStream, task,
self.resources, opList).then(function () {
return opList;
});
});
@ -212,7 +212,7 @@ var Page = (function PageClosure() {
}
var annotationsReadyPromise = Annotation.appendToOperatorList(
annotations, pageOpList, pdfManager, partialEvaluator, intent);
annotations, pageOpList, pdfManager, partialEvaluator, task, intent);
return annotationsReadyPromise.then(function () {
pageOpList.flush(true);
return pageOpList;
@ -220,7 +220,7 @@ var Page = (function PageClosure() {
});
},
extractTextContent: function Page_extractTextContent() {
extractTextContent: function Page_extractTextContent(task) {
var handler = {
on: function nullHandlerOn() {},
send: function nullHandlerSend() {}
@ -249,6 +249,7 @@ var Page = (function PageClosure() {
self.fontCache);
return partialEvaluator.getTextContent(contentStream,
task,
self.resources);
});
},

View File

@ -125,6 +125,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask,
operatorList,
task,
initialState) {
var matrix = xobj.dict.getArray('Matrix');
var bbox = xobj.dict.getArray('BBox');
@ -157,7 +158,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
return this.getOperatorList(xobj,
return this.getOperatorList(xobj, task,
(xobj.dict.get('Resources') || resources), operatorList, initialState).
then(function () {
operatorList.addOp(OPS.paintFormXObjectEnd, []);
@ -269,7 +270,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
operatorList,
operatorList, task,
stateManager) {
var smaskContent = smask.get('G');
var smaskOptions = {
@ -277,13 +278,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
backdrop: smask.get('BC')
};
return this.buildFormXObject(resources, smaskContent, smaskOptions,
operatorList, stateManager.state.clone());
operatorList, task, stateManager.state.clone());
},
handleTilingType:
function PartialEvaluator_handleTilingType(fn, args, resources,
pattern, patternDict,
operatorList) {
operatorList, task) {
// Create an IR of the pattern code.
var tilingOpList = new OperatorList();
// Merge the available resources, to prevent issues when the patternDict
@ -291,8 +292,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var resourcesArray = [patternDict.get('Resources'), resources];
var patternResources = Dict.merge(this.xref, resourcesArray);
return this.getOperatorList(pattern, patternResources, tilingOpList).then(
function () {
return this.getOperatorList(pattern, task, patternResources,
tilingOpList).then(function () {
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
operatorList.addDependencies(tilingOpList.dependencies);
@ -305,7 +306,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
handleSetFont:
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
operatorList, state) {
operatorList, task, state) {
// TODO(mack): Not needed?
var fontName;
if (fontArgs) {
@ -319,8 +320,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (!translated.font.isType3Font) {
return translated;
}
return translated.loadType3Data(self, resources, operatorList).then(
function () {
return translated.loadType3Data(self, resources, operatorList, task).
then(function () {
return translated;
});
}).then(function (translated) {
@ -367,8 +368,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
setGState: function PartialEvaluator_setGState(resources, gState,
operatorList, xref,
stateManager) {
operatorList, task,
xref, stateManager) {
// This array holds the converted/processed state data.
var gStateObj = [];
var gStateMap = gState.map;
@ -392,8 +393,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break;
case 'Font':
promise = promise.then(function () {
return self.handleSetFont(resources, null, value[0],
operatorList, stateManager.state).
return self.handleSetFont(resources, null, value[0], operatorList,
task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]);
@ -412,7 +413,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (isDict(dict)) {
promise = promise.then(function () {
return self.handleSMask(dict, resources, operatorList,
stateManager);
task, stateManager);
});
gStateObj.push([key, true]);
} else {
@ -593,7 +594,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args,
cs, patterns, resources, xref) {
cs, patterns, resources, task, xref) {
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
@ -606,7 +607,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (typeNum === TILING_PATTERN) {
var color = cs.base ? cs.base.getRgb(args, 0) : null;
return this.handleTilingType(fn, color, resources, pattern,
dict, operatorList);
dict, operatorList, task);
} else if (typeNum === SHADING_PATTERN) {
var shading = dict.get('Shading');
var matrix = dict.get('Matrix');
@ -623,6 +624,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
getOperatorList: function PartialEvaluator_getOperatorList(stream,
task,
resources,
operatorList,
initialState) {
@ -641,6 +643,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, i, ii, cs;
while (!(stop = timeSlotManager.check())) {
@ -683,7 +686,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (type.name === 'Form') {
stateManager.save();
return self.buildFormXObject(resources, xobj, null,
operatorList,
operatorList, task,
stateManager.state.clone()).
then(function () {
stateManager.restore();
@ -707,8 +710,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case OPS.setFont:
var fontSize = args[1];
// eagerly collect all fonts
return self.handleSetFont(resources, args, null,
operatorList, stateManager.state).
return self.handleSetFont(resources, args, null, operatorList,
task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
@ -814,7 +817,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.fillColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setFillColorN,
args, cs, patterns, resources, xref).then(function() {
args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
@ -825,7 +828,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.strokeColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setStrokeColorN,
args, cs, patterns, resources, xref).then(function() {
args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
@ -859,8 +862,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
var gState = extGState.get(dictName.name);
return self.setGState(resources, gState, operatorList, xref,
stateManager).then(function() {
return self.setGState(resources, gState, operatorList, task,
xref, stateManager).then(function() {
next(resolve, reject);
}, reject);
case OPS.moveTo:
@ -898,7 +901,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) {
deferred.then(function () {
next(resolve, reject);
});
}, reject);
return;
}
// Some PDFs don't close all restores inside object/form.
@ -910,7 +913,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
});
},
getTextContent: function PartialEvaluator_getTextContent(stream, resources,
getTextContent: function PartialEvaluator_getTextContent(stream, task,
resources,
stateManager) {
stateManager = (stateManager || new StateManager(new TextState()));
@ -1088,6 +1092,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, args = [];
while (!(stop = timeSlotManager.check())) {
@ -1243,7 +1248,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
stateManager.transform(matrix);
}
return self.getTextContent(xobj,
return self.getTextContent(xobj, task,
xobj.dict.get('Resources') || resources, stateManager).
then(function (formTextContent) {
Util.appendToArray(bidiTexts, formTextContent.items);
@ -1283,7 +1288,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) {
deferred.then(function () {
next(resolve, reject);
});
}, reject);
return;
}
resolve(textContent);
@ -1848,7 +1853,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
]);
this.sent = true;
},
loadType3Data: function (evaluator, resources, parentOperatorList) {
loadType3Data: function (evaluator, resources, parentOperatorList, task) {
assert(this.font.isType3Font);
if (this.type3Loaded) {
@ -1865,7 +1870,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
var glyphStream = charProcs[key];
var operatorList = new OperatorList();
return evaluator.getOperatorList(glyphStream, fontResources,
return evaluator.getOperatorList(glyphStream, task, fontResources,
operatorList).then(function () {
charProcOperatorList[key] = operatorList.getIR();

View File

@ -22,11 +22,42 @@
'use strict';
var WorkerTask = (function WorkerTaskClosure() {
function WorkerTask(name) {
this.name = name;
this.terminated = false;
this._capability = createPromiseCapability();
}
WorkerTask.prototype = {
get finished() {
return this._capability.promise;
},
finish: function () {
this._capability.resolve();
},
terminate: function () {
this.terminated = true;
},
ensureNotTerminated: function () {
if (this.terminated) {
throw new Error('Worker task was terminated');
}
}
};
return WorkerTask;
})();
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
setup: function wphSetup(handler) {
var pdfManager;
var terminated = false;
var cancelXHRs = null;
var WorkerTasks = [];
function ensureNotTerminated() {
if (terminated) {
@ -34,6 +65,16 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}
}
function startWorkerTask(task) {
WorkerTasks.push(task);
}
function finishWorkerTask(task) {
task.finish();
var i = WorkerTasks.indexOf(task);
WorkerTasks.splice(i, 1);
}
function loadDocument(recoveryMode) {
var loadDocumentCapability = createPromiseCapability();
@ -413,17 +454,25 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
});
handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
pdfManager.getPage(data.pageIndex).then(function(page) {
var pageIndex = data.pageIndex;
pdfManager.getPage(pageIndex).then(function(page) {
var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
startWorkerTask(task);
var pageNum = data.pageIndex + 1;
var pageNum = pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
page.getOperatorList(handler, data.intent).then(function(operatorList) {
page.getOperatorList(handler, task, data.intent).then(
function(operatorList) {
finishWorkerTask(task);
info('page=' + pageNum + ' - getOperatorList: time=' +
(Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
}, function(e) {
finishWorkerTask(task);
if (task.terminated) {
return; // ignoring errors from the terminated thread
}
var minimumStackMessage =
'worker.js: while trying to getPage() and getOperatorList()';
@ -458,13 +507,23 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}, this);
handler.on('GetTextContent', function wphExtractText(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) {
var pageNum = data.pageIndex + 1;
var pageIndex = data.pageIndex;
return pdfManager.getPage(pageIndex).then(function(page) {
var task = new WorkerTask('GetTextContent: page ' + pageIndex);
startWorkerTask(task);
var pageNum = pageIndex + 1;
var start = Date.now();
return page.extractTextContent().then(function(textContent) {
return page.extractTextContent(task).then(function(textContent) {
finishWorkerTask(task);
info('text indexing: page=' + pageNum + ' - time=' +
(Date.now() - start) + 'ms');
return textContent;
}, function (reason) {
finishWorkerTask(task);
if (task.terminated) {
return; // ignoring errors from the terminated thread
}
throw reason;
});
});
});
@ -482,6 +541,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
if (cancelXHRs) {
cancelXHRs();
}
var waitOn = [];
WorkerTasks.forEach(function (task) {
waitOn.push(task.finished);
task.terminate();
});
return Promise.all(waitOn).then(function () {});
});
}
};

View File

@ -883,15 +883,20 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.destroyed = true;
this.transport.pageCache[this.pageIndex] = null;
var waitOn = [];
Object.keys(this.intentStates).forEach(function(intent) {
var intentState = this.intentStates[intent];
intentState.renderTasks.forEach(function(renderTask) {
var renderCompleted = renderTask.capability.promise.
catch(function () {}); // ignoring failures
waitOn.push(renderCompleted);
renderTask.cancel();
});
}, this);
this.objs.clear();
this.annotationsPromise = null;
this.pendingCleanup = false;
return Promise.all(waitOn);
},
/**
@ -1054,15 +1059,21 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.destroyed = true;
this.destroyCapability = createPromiseCapability();
var waitOn = [];
// We need to wait for all renderings to be completed, e.g.
// timeout/rAF can take a long time.
this.pageCache.forEach(function (page) {
if (page) {
page._destroy();
waitOn.push(page._destroy());
}
});
this.pageCache = [];
this.pagePromises = [];
var self = this;
this.messageHandler.sendWithPromise('Terminate', null).then(function () {
// We also need to wait for the worker to finish its long running tasks.
var terminated = this.messageHandler.sendWithPromise('Terminate', null);
waitOn.push(terminated);
Promise.all(waitOn).then(function () {
FontLoader.clear();
if (self.worker) {
self.worker.terminate();

View File

@ -1,13 +1,13 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* globals expect, it, describe, PartialEvaluator, StringStream, OPS,
OperatorList, waitsFor, runs, Dict, Name, Stream */
OperatorList, waitsFor, runs, Dict, Name, Stream, WorkerTask */
'use strict';
describe('evaluator', function() {
function XrefMock(queue) {
this.queue = queue;
this.queue = queue || [];
}
XrefMock.prototype = {
fetchIfRef: function() {
@ -35,7 +35,9 @@ describe('evaluator', function() {
var done = false;
runs(function () {
var result = new OperatorList();
evaluator.getOperatorList(stream, resources, result).then(function () {
var task = new WorkerTask('OperatorListCheck');
evaluator.getOperatorList(stream, task, resources, result).then(
function () {
check(result);
done = true;
});
@ -259,4 +261,48 @@ describe('evaluator', function() {
});
});
});
describe('thread control', function() {
it('should abort operator list parsing', function () {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qqQQ');
var resources = new ResourcesMock();
var done = false;
runs(function () {
var result = new OperatorList();
var task = new WorkerTask('OperatorListAbort');
task.terminate();
evaluator.getOperatorList(stream, task, resources, result).catch(
function () {
done = true;
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(0);
});
});
waitsFor(function () {
return done;
});
});
it('should abort text parsing parsing', function () {
var resources = new ResourcesMock();
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qqQQ');
var done = false;
runs(function () {
var task = new WorkerTask('TextContentAbort');
task.terminate();
evaluator.getTextContent(stream, task, resources).catch(
function () {
done = true;
});
});
waitsFor(function () {
return done;
});
});
});
});