Advanced features of PDFNetJS Full

In this document
chevron_rightPDFNetJS locking
chevron_rightMemory management

To get started with setting up a PDFNetJS project, refer to the getting starting guide.

PDFNetJS contains several methods for more advanced control over its processes via locking and memory management. This guide will provide an explanation of how to use these features and will also shed some light on the backend processes of PDFNetJS.

Understanding PDFNetJS backends

For optimal performance, PDFNetJS uses two different backends to run its processes, PNaCl and Emscripten.

  • PNaCl has good performance and supports multithreading but only works on Google Chrome. It also requires an upfront compilation step that is CPU intensive. Once PNaCl compilation has completed, it will be cached and will not need to be compiled again on the next WebViewer load.
  • Emscripten (which compiles both WebAssembly and asm.js implementations) starts up a little faster and is compatible with most browsers, but has slower runtime performance.

On Google Chrome the PNaCl backend will be used by default, while other browsers will use the Emscripten backend.

PDFNetJS locking

Locking prevents multiple simultaneous accesses to a PDF document in a PDFNetJS program so that it cannot be changed by outside operations. PDFNetJS contains three main types of locking and unlocking statements:

[PDFDoc].lock() / [PDFDoc].unlock() - Locks a PDF document to prevent competing threads from accessing the document at the same time. Threads attempting to access the document will wait in suspended state until the thread that owns the lock calls doc.Unlock().

Documents are automatically unlocked upon process completion if the user code is being run PDFNet.runGeneratorWithCleanup(main()).

[PDFDoc].lockRead() / [PDFDoc].unlockRead() - Locks the document to prevent competing write threads (using Lock()) from accessing the document at the same time. Read-only threads however, will be allowed to access the document.

Threads attempting to obtain write access to the document will wait in suspended state until the thread that owns the lock calls doc.unlockRead(). Documents are automatically unlocked upon process completion if the user code is being run with PDFNet.runGeneratorWithCleanup(main()).

Avoiding deadlocks
To avoid deadlocks, obtaining a write lock while holding a read lock is not permitted and will throw an exception. If this situation is encountered please either unlock the read lock before the write lock is obtained or acquire a write lock (rather than read lock) in the first place.

Example of manual document locking and unlocking:

function* main() {
  try {
    var doc = yield PDFNet.PDFDoc.create();
    doc.initSecurityHandler();
    // documents require locking
    doc.lock();
    // do stuff with document
  } catch (err){
    console.log(err);
  } finally {
    // unlocks the document
    yield doc.unlock();
  }
}
PDFNet.runGeneratorWithoutCleanup(main());
// if you use PDFNet.runGeneratorWithCleanup(main()), no need to unlock

PDFNet.beginOperation() / PDFNet.finishOperation() - beginOperation() locks all worker operations on PDFNet.

Both PDFNet.runGeneratorWithCleanup() and PDFNet.runGeneratorWithoutCleanup() call beginOperation() and end with finishOperation(), so beginOperation() and finishOperation() should only be used when PDFNet needs to be unlocked in the middle of a process.

function* main() {
  // ...
}
// Automatically locks all worker operations
PDFNet.runGeneratorWithCleanup(main());

Running multiple unrelated PDFNetJS operations simultaneously may result in race conditions. This can be resolved by synchronizing the PDFNetJS operations using a queue. The deck.js sample demonstrates this by queuing its renderPage calls to prevent race conditions on its single shared PDFDraw object.

For this reason, calling beginOperation() a second time before finishOperation() is called will cause an exception to be thrown.

If the need arises, this exception can be disabled by passing in an empty object into beginOperation with the object's "allowMultipleInstances" member set to true, but this is generally not recommended.

// How to allow multiple PDFNet.beginOperation() calls
var optionsObj = {};
optionsObj.allowMultipleInstances = true;
yield PDFNet.beginOperation(optionsObj);

There are some cases where unlocking in the middle of a PDFNetJS proccess may be required. This usually happens if requirePage() needs to be called on a document.

The ViewerEdit sample on the samples page shows a situation where manual unlocking and re-locking may be used.

var doc = yield PDFNet.PDFDoc.create();
doc.initSecurityHandler();
doc.lock();
// ...

// This section is only required to ensure the page is available
// for incremental download. At the moment the call to requirePage must be
// wrapped in this manner to avoid potential deadlocks and
// allow other parts of the viewer to run while the page is being downloaded.
doc.unlock();
yield PDFNet.finishOperation();
// requirePage(pageNum) ensures that the first page is downloaded before it is accessed.
yield doc.requirePage(1);
yield PDFNet.beginOperation();
doc.lock();

Memory management

PDFNetJS automatically cleans up all objects in a process initialized using PDFNet.runGeneratorWithCleanup() once the process has finished running. But for more precise memory management, there are two manual ways to deallocate objects:

[Obj].destroy() - Deallocates individual objects. Not all PDFNetJS objects have and require this function however.

PDFNet.startDeallocateStack() / PDFNet.endDeallocateStack() - Stack-based deallocation. Calling endDeallocateStack() will deallocate all objects that were created since the last call to PDFNet.startDeallocateStack().

In general, stack-based deallocation is recommended because not all objects have a .destroy() method and it is easier to manage for larger sections of code.

Example of default/automatic deallocation:

function* main() {
  try {
    // documents require deallocation
    var doc = yield PDFNet.PDFDoc.create();
    // object that requires deallocation
    var page_iter = yield doc.getPageIterator();
    // object that requires deallocation
    var writer = yield PDFNet.ElementWriter.create();
  } catch (err){
    console.log(err);
  }
}
// deallocates all objects once main() finishes running
PDFNet.runGeneratorWithCleanup(main());

Example of individual deallocation:

function* main() {
  try {
    var doc = yield PDFNet.PDFDoc.create();
    var page_iter = yield doc.getPageIterator();
    var writer = yield PDFNet.ElementWriter.create();
  } catch (err){
    console.log(err);
  } finally {
    yield doc.destroy();
    yield page_iter.destroy();
    yield writer.destroy();
  }
}
PDFNet.runGeneratorWithoutCleanup(main());

Example of stack-based deallocation:

function* main() {
  try {
    yield PDFNet.startDeallocateStack();
    var doc = yield PDFNet.PDFDoc.create();
    var page_iter = yield doc.getPageIterator();
    var writer = yield PDFNet.ElementWriter.create();
    yield PDFNet.endDeallocateStack();
  } catch (err){
    console.log(err);
  }
}
PDFNet.runGeneratorWithoutCleanup(main());