react-qr-reader icon indicating copy to clipboard operation
react-qr-reader copied to clipboard

Chrome: out of memory due to web worker. Chrome not kicking off GC

Open ch08532 opened this issue 3 years ago • 0 comments

Not sure if anybody is seeing this issue but I'll raise it here.

We've been using this component in one of our departmental COVID contact tracing apps. We built a very simple react app that runs hours on end scanning qr codes. It runs on an android tablet. As part of this app, the react-qr-code component executes scans at 2hz (delay = 500 ms).

Recently, we have done a tablet update and noticed our app now will occasionally run out of memory. We have also confirmed this on Chrome (latest build on Win10). After hours of digging I have traced it down to the web worker with the allocation of the jsQR object. The Chrome version that worked without issue is version 85.0.4183. So from that version to now the gc algo must have changed.

Now, when I start up Chrome dev tools and manually perform a gc, the memory is freed and all is normal.

We have also pulled the forked react-qr-reader (modern-react-qr-reader) and the problem exists there as well.

The only fix (hack?) that worked for us is to periodically bounce the web worker which triggers the gc to run.

example in the scan function (I modified the lib/index.js in the node_modules folder directly):

  var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
         
  // Recreate web worker
  if (!this.worker) {
     this.worker = new Worker(URL.createObjectURL(workerBlob));
     this.worker.onmessage = this.handleWorkerMessage;
  }
  //post image data to web worker for processing
​  this.worker.postMessage(imageData);

and updated the handleWorkerMessage function to terminate the worker on every 50th scan interval:

if (!legacyMode && typeof delay == 'number' && this.worker) {

        //chrome gc not firing so this is the hack to force gc to run
        if (count % 50 === 0) {
        this.worker.terminate();
        this.worker = undefined;
        count = 0;
        }
        count++;
        this.timeout = setTimeout(this.check, delay);
      }

I've also looked at the demo site and if you watch the task manager the memory does in fact grow but it appears the gc kicks in a bit more reliably.

Also, we changed the web worker postMessage call to use a transferrable object but that too did not solve the problem (however allocated less memory as we are not doing a copy of imageData).

ex:

       let r = new ArrayBuffer(8);
       r = imageData.data.buffer
       this.worker.postMessage(r, [ r ]);

and in the webworker changed:

var e=jsQR(new Uint8ClampedArray(o.data),600,600); //hardcoded width and height for testing

Overall, the problem randomly manifests if running for over 1/2 hr or so and occasionally running much longer...all dependent on when the gc kicks off.

ch08532 avatar Sep 22 '21 15:09 ch08532