I need some help with finding a memory leak in a small, Browser / WebWorker JavaScript. I tracked it down into this little piece of code:
/**
* Resizes an Image
*
* @function scaleImage
* @param {object} oImageBlob blob of image
* @param {int} iHeight New height of Image
* @return {ImageBitmap} ImageBitmap Object
*/
async function scaleImage(oImageBlob, iHeight) {
var img = await self.createImageBitmap(oImageBlob);
var iWidth = Math.round( ( img.width / img.height ) * iHeight);
var canvas = new OffscreenCanvas(iWidth,iHeight);
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return(canvas.transferToImageBitmap());
}
It's called from:
[inside a web worker: Some looping that calls this about 1200 times while parsind files from a s3 bucket ...]
var oImageBlob = await new Response(oS3Object.Body, {}).blob();
var oThumbnail = await scaleImage(oImageBlob, 100);
await IDBputData(oInput.sFileKey, oImageBlob, oInput.sStore, oThumbnail)
[... end of the loop]
The other interior function is
/**
* Put information to IndexedDB
*
* @function IDBputData
* @param {string} sKey key of information
* @param {string} sValue information to upload
* @param {string} sStore name of object store
* @param {object} oThumbnail optrional, default: null, thumbnail image
* @return {object} - SUCCESS: array, IndexedDB Identifyer "key"
* - FAIL: Error Message
*/
async function IDBputData(sKey, sValue, sStore, oThumbnail=null) {
var oGeneratedKeys = {};
if(sStore=="panelStore"){
oGeneratedKeys = await getKeyfromSKey(sKey);
}
return new Promise((resolve, reject) => {
const tx = oConn.transaction(sStore, 'readwrite');
const store = tx.objectStore(sStore);
var request = {}
request = store.put({panelkey: oGeneratedKeys.panelkey, layerkey: oGeneratedKeys.layerkey, countrycode: oGeneratedKeys.countrycode, value: sValue, LastModified: new Date(), oThumbnail: oThumbnail});
request.onsuccess = () => (oThumbnail.close(),resolve(request.result));
request.onerror = () => (oThumbnail.close(),reject(request.error));
});
}
When I run it this way in Chrome, it will consume every bit of RAM I've got free (around 8 GB) and then crash. (Laptop with shared RAM for CPU/GPU).
When I change
var oThumbnail = await scaleImage(oImageBlob, 100);
to
var oThumbnail = null;
RAM consumption of Chrome stays rather fixed around 800 MB, so there must be something with the topmost function "scaleImage".
I tried tweaking it, but with no success.
/**
* Resizes an Image
*
* @function scaleImage
* @param {object} oImageBlob blob of image
* @param {int} iHeight New height of Image
* @return {ImageBitmap} ImageBitmap Object
*/
async function scaleImage(oImageBlob, iHeight) {
var img = await self.createImageBitmap(oImageBlob);
var iWidth = Math.round( ( img.width / img.height ) * iHeight);
var canvas = new OffscreenCanvas(iWidth,iHeight);
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var oImageBitmap = canvas.transferToImageBitmap();
ctx = null;
canvas = null;
iWidth = null;
img = null;
return(oImageBitmap);
}
Any help is very much appreciated.
For the ImageBitmap to release its bitmap data the most efficient way, you have to call its
.close()
method once you're done with it.But actually, you don't need this
scaleImage
function.createImageBitmap()
has aresizeHeight
option, and if you use it without theresizeWidth
one, you'll resize your image by keeping the aspect-ratio exacty like you are doing in your function, except that it won't need to assign the bitmap twice.Once you have this resized ImageBitmap, you can transfer it to a BitmapRenderingContext (which will internally
close()
the original ImageBitmap) and call thetransferToBlob()
from that renderer. This should be lighter for your computer.