From f11a4ba7503d031a40e8dff6b1934f17372b0893 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 4 Sep 2019 11:20:14 +0200 Subject: [PATCH] Transfer, rather than copy, CMap data to the worker-thread It recently occurred to me that the CMap data should be an excellent candidate for transfering. This will help reduce peak memory usage for PDF documents using CMaps, since transfering of data avoids duplicating it on both the main- and worker-threads. Unfortunately it's not possible to actually transfer data when *returning* data through `sendWithPromise`, and another solution had to be used. Initially I looked at using one message for requesting the data, and another message for returning the actual CMap data. While that should have worked, it would have meant adding a lot more complexity particularly on the worker-thread. Hence the simplest solution, at least in my opinion, is to utilize `sendWithStream` since that makes it *really* easy to transfer the CMap data. (This required PR 11115 to land first, since otherwise CMap fetch errors won't propagate correctly to the worker-thread.) Please note that the patch *purposely* only changes the API to Worker communication, and not the API *itself* since changing the interface of `CMapReaderFactory` would be a breaking change. Furthermore, given the relatively small size of the `.bcmap` files (the largest one is smaller than the default range-request size) streaming doesn't really seem necessary either. --- src/core/evaluator.js | 20 ++++++++++++++++++-- src/display/api.js | 21 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 27c7256a3..2c3c80964 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -78,8 +78,24 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (this.builtInCMapCache.has(name)) { return this.builtInCMapCache.get(name); } - const data = await this.handler.sendWithPromise('FetchBuiltInCMap', - { name, }); + const readableStream = this.handler.sendWithStream('FetchBuiltInCMap', { + name, + }); + const reader = readableStream.getReader(); + + const data = await new Promise(function(resolve, reject) { + function pump() { + reader.read().then(function({ value, done, }) { + if (done) { + return; + } + resolve(value); + pump(); + }, reject); + } + pump(); + }); + if (data.compressionType !== CMapCompressionType.NONE) { // Given the size of uncompressed CMaps, only cache compressed ones. this.builtInCMapCache.set(name, data); diff --git a/src/display/api.js b/src/display/api.js index a964d52ff..1be287ac6 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -2230,11 +2230,26 @@ class WorkerTransport { }); }); - messageHandler.on('FetchBuiltInCMap', (data) => { + messageHandler.on('FetchBuiltInCMap', (data, sink) => { if (this.destroyed) { - return Promise.reject(new Error('Worker was destroyed')); + sink.error(new Error('Worker was destroyed')); + return; } - return this.CMapReaderFactory.fetch(data); + let fetched = false; + + sink.onPull = () => { + if (fetched) { + sink.close(); + return; + } + fetched = true; + + this.CMapReaderFactory.fetch(data).then(function(builtInCMap) { + sink.enqueue(builtInCMap, 1, [builtInCMap.cMapData.buffer]); + }).catch(function(reason) { + sink.error(reason); + }); + }; }); }