JavaScript中内存泄漏(WebWorker、画布、 IndexedDB )

fjaof16o  于 2022-12-09  发布在  IndexedDB
关注(0)|答案(1)|浏览(259)

我需要一些帮助来发现一个小的浏览器/ WebWorker JavaScript中的内存泄漏。我在这段代码中找到了它:

/**
     * 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());
    }

它的调用来自:

[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]

另一个内部功能是

/**
     * 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));
        });
    }

当我在Chrome中以这种方式运行它时,它会消耗掉我所有的空闲内存(大约8 GB),然后崩溃(笔记本电脑与CPU/GPU共享内存)。
当我改变

var oThumbnail = await scaleImage(oImageBlob, 100);

var oThumbnail = null;

Chrome的RAM消耗保持在800 MB左右,所以必须有一些最上面的函数“scaleImage”。
我试着调整它,但没有成功。

/**
 * 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);
}

任何帮助都是非常感谢的。

zxlwwiss

zxlwwiss1#

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 a resizeHeight option, and if you use it without the resizeWidth 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 the transferToBlob() from that renderer. This should be lighter for your computer.

async function worker_script() {
  const blob = await fetch( "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png" ).then( (resp) => resp.blob() );
  // resize from createImageBitmap directly
  const img = await createImageBitmap( blob , { resizeHeight: 100 } );
  const canvas = new OffscreenCanvas( img.width, img.height );
  canvas.getContext( "bitmaprenderer" )
    .transferFromImageBitmap( img ); // this internally closes the ImageBitmap
  const resized_blob = await canvas.convertToBlob();
  // putInIDB( resized_blob );
  // for the demo we pass that Blob to main, but one shouldn't have to do that
  // show the canvas instead ;)
  postMessage( { blob: resized_blob, width: img.width } );
  // to be safe, we can even resize our canvas to 0x0 to free its bitmap entirely
  canvas.width = canvas.height = 0;
}

// back to main
const worker = new Worker( getWorkerURL() );
worker.onmessage = ({ data: { blob, width } }) => {
  const img = new Image();
  img.src = URL.createObjectURL( blob );
  img.onload = (evt) => URL.revokeObjectURL( img.src );
  document.body.append( img );
  console.log( "ImageBitmap got detached?", width === 0 );
};

function getWorkerURL() {
  const content = "!" + worker_script.toString() + "();";
  const blob = new Blob( [ content ], { type: "text/javascript" } );
  return URL.createObjectURL( blob );
}

相关问题