Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak when loading images on the canvas in Node.js #5102

Closed
0ro opened this issue Jul 13, 2018 · 29 comments · Fixed by #5142
Closed

Memory leak when loading images on the canvas in Node.js #5102

0ro opened this issue Jul 13, 2018 · 29 comments · Fixed by #5142

Comments

@0ro
Copy link

0ro commented Jul 13, 2018

Version

2.3.3

Test Case

const http = require(`http`);
const {fabric} = require(`fabric`);

const server = http.createServer((req, res) => {
  let {url} = req;
  if (url === `/`) {
    console.log(`Rss ${+process.memoryUsage().rss / 1024 / 1024 }MB`);
    console.log(`Heap ${+process.memoryUsage().heapUsed / 1024 / 1024 }MB`);

    const canvas = new fabric.Canvas(`canvas`, {
      width: 600,
      height: 600
    });

    const JSON = `{"version":"2.3.3","objects":[{"type":"image","version":"2.3.3","originX":"left","originY":"top","left":264,"top":278.5,"width":600,"height":600,"fill":"rgb(0, 0, 0)","stroke":null,"strokeWidth":0,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":0.39,"scaleY":0.39,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"crossOrigin":"","cropX":0,"cropY":0,"src":"https://content0.printio.ru/assets/images/large/26a1fb4ae16cbe53aaa07ab44e31f44cd73fd5d3.png?1525104682","filters":[]}],"background":"red"}`;

    canvas.loadFromJSON(JSON, function () {
      let pngStream = canvas.createPNGStream();

      res.writeHead(200, {
        'Content-Type': `image/png`
      });

      pngStream.on(`data`, function (chunk) {
        res.write(chunk);
      });

      pngStream.on(`error`, function (exception) {
        console.log(`Error on stream ${exception}`);
      });

      pngStream.on(`end`, function () {
        res.end();
        canvas.clear();
        canvas.dispose();
        if (global.gc) {
          global.gc();
        }
      });
    });
  }
});
server.listen(8124, () => {
  console.log(`Server is runing on http://localhost:8124`);
});
server git:(master) ✗ node --expose-gc .
Server is runing on http://localhost:8124
Rss 76.65625MB
Heap 23.975570678710938MB
Rss 82.1875MB
Heap 24.32623291015625MB
Rss 88.54296875MB
Heap 24.335586547851562MB
Rss 91.65234375MB
Heap 22.522903442382812MB
Rss 92.5MB
Heap 22.56616973876953MB
Rss 94.1953125MB
Heap 22.5391845703125MB

Steps to reproduce

Run the server script and make several requests to the address in the browser http://localhost:8124

Expected Behavior

process.memoryUsage().rss should not grow

Actual Behavior

When you create an http server on node.js that handles the requests in json and response the image created on the basis of these data. Each request to the server increases the process.memoryUsage().rss, but remains almost unchanged process.memoryUsage().heapUsed.

@asturur
Copy link
Member

asturur commented Jul 13, 2018

Did you try with StaticCanvas? just to reduce the area where the leak could be.
I feel like some reference may be still be left around in JSDOM but if you look at the dispose code is trying to clear everything.

You do not need to clear and dispose, dispose is taking care of everything,
Clear is firing an extra render, that it may be what keeps something alive in memory.

@0ro
Copy link
Author

0ro commented Jul 13, 2018

@asturur I tried just this with StaticCanvas and removed the canvas.clear but the behavior persisted. I also think the cause of the JS DOM leak, but I don't know how to clean it? I read that you can call window.close (). I tried to call the fabric.window.close (), but after this call, subsequent queries draw nothing.

@asturur
Copy link
Member

asturur commented Jul 14, 2018

const http = require(`http`);
const {fabric} = require(`fabric`);

const JSON = `{"version":"2.3.3","objects":[],"background":"red"}`;

const server = http.createServer((req, res) => {
  let {url} = req;
  if (url === `/`) {
    console.log(`Rss ${+process.memoryUsage().rss / 1024 / 1024 }MB`);
    console.log(`Heap ${+process.memoryUsage().heapUsed / 1024 / 1024 }MB`);

    const canvas = new fabric.StaticCanvas(null, {
      width: 600,
      height: 600
    });

    canvas.loadFromJSON(JSON, function () {
      let pngStream = canvas.createPNGStream();

      res.writeHead(200, {
        'Content-Type': `image/png`
      });

      pngStream.on(`data`, function (chunk) {
        res.write(chunk);
      });

      pngStream.on(`error`, function (exception) {
        console.log(`Error on stream ${exception}`);
      });

      pngStream.on(`end`, function () {
        canvas.dispose();
        res.end();
      });
    });
    if (global.gc) {
      console.log('executing GC')
      global.gc();
    }
  }
});
server.listen(8124, () => {
  console.log(`Server is runing on http://localhost:8124`);
});

with this minimal test case there is a small leak anyway, like 1 meg ever 100 requests. That i do not think is normal at all.

Putting the GC outside the callback improve the situation anyway, i do think fabric maybe should do some explicit cleaning of resources on images first of all.
A new canvas is created as a new image in your script, so the canvas is cleared corectly but the image not.

@asturur
Copy link
Member

asturur commented Jul 14, 2018

Look i did this quick check

image

Changing this code does not solve the situation but makes it more stable, so the solution is somewhere around here.

Server is runing on http://localhost:8124
Rss 67.51953125MB
Heap 25.09008026123047MB
executing GC
Rss 76.75390625MB
Heap 25.58637237548828MB
executing GC
Rss 77.21875MB
Heap 25.53907012939453MB
executing GC
Rss 76.62109375MB
Heap 25.490982055664062MB
executing GC
Rss 76.5546875MB
Heap 25.50659942626953MB
executing GC
Rss 78.50390625MB
Heap 25.497146606445312MB
executing GC
Rss 75.953125MB
Heap 25.50572967529297MB
executing GC
Rss 75.8125MB
Heap 25.50189971923828MB
executing GC
^C
abogazzi@abogazzi-mbp > /abogazzi/leak $ node  --expose-gc leak.js
Server is runing on http://localhost:8124
Rss 86.1875MB
Heap 26.194480895996094MB
executing GC
Rss 95.73828125MB
Heap 27.477821350097656MB
executing GC
Rss 74.05078125MB
Heap 25.223106384277344MB
executing GC
Rss 74.44921875MB
Heap 25.505760192871094MB
executing GC
Rss 75.58984375MB
Heap 25.497543334960938MB
executing GC
Rss 76.8671875MB
Heap 25.371742248535156MB
executing GC
Rss 76.72265625MB
Heap 25.58747100830078MB
executing GC
Rss 77.1953125MB
Heap 25.454025268554688MB
executing GC
Rss 82.2265625MB
Heap 25.82940673828125MB
executing GC
Rss 77.859375MB
Heap 25.491226196289062MB
executing GC
Rss 80.359375MB
Heap 25.772613525390625MB
executing GC
Rss 80.22265625MB
Heap 25.496383666992188MB
executing GC
Rss 80.015625MB
Heap 25.66327667236328MB
executing GC
Rss 80.30078125MB
Heap 25.750648498535156MB
executing GC
Rss 79.18359375MB
Heap 25.519454956054688MB
executing GC
Rss 78.875MB
Heap 25.775245666503906MB
executing GC
Rss 79.09765625MB
Heap 25.496795654296875MB
executing GC
Rss 78.91796875MB
Heap 25.775054931640625MB
executing GC
Rss 79.00390625MB
Heap 25.658523559570312MB
executing GC
Rss 79.05078125MB
Heap 25.520339965820312MB
executing GC
Rss 78.65234375MB
Heap 25.664024353027344MB
executing GC
Rss 78.5078125MB
Heap 25.63727569580078MB
executing GC
Rss 78.6796875MB
Heap 25.69341278076172MB
executing GC
Rss 78.59375MB
Heap 25.636734008789062MB
executing GC
Rss 79.1640625MB
Heap 25.689308166503906MB
executing GC
Rss 79.13671875MB
Heap 25.664047241210938MB
executing GC
Rss 79.3046875MB
Heap 25.696701049804688MB
executing GC
Rss 80.65234375MB
Heap 25.60210418701172MB
executing GC
Rss 82.05859375MB
Heap 25.741348266601562MB
executing GC
Rss 82.0703125MB
Heap 25.740798950195312MB
executing GC
Rss 82.140625MB
Heap 25.73542022705078MB
executing GC
Rss 82.40625MB
Heap 25.651344299316406MB
executing GC
Rss 82.66796875MB
Heap 25.878387451171875MB
executing GC
Rss 82.84375MB
Heap 25.75762176513672MB
executing GC
Rss 82.9140625MB
Heap 25.698402404785156MB
executing GC
Rss 82.953125MB
Heap 25.711402893066406MB
executing GC
Rss 83.015625MB
Heap 25.767616271972656MB
executing GC
Rss 89.84375MB
Heap 26.072616577148438MB
executing GC
Rss 88.6328125MB
Heap 25.758460998535156MB
executing GC
Rss 88.3984375MB
Heap 25.961624145507812MB
executing GC
Rss 88.8046875MB
Heap 25.788681030273438MB
executing GC
Rss 88.94921875MB
Heap 25.839813232421875MB
executing GC
Rss 88.984375MB
Heap 25.870155334472656MB
executing GC
Rss 90.546875MB
Heap 26.09229278564453MB
executing GC
Rss 90.87890625MB
Heap 25.992050170898438MB
executing GC
Rss 91.69921875MB
Heap 26.083770751953125MB
executing GC
Rss 92.01953125MB
Heap 25.93645477294922MB
executing GC
Rss 92.09765625MB
Heap 25.99181365966797MB
executing GC
Rss 92.15625MB
Heap 26.02275848388672MB
executing GC
Rss 92.453125MB
Heap 26.06237030029297MB
executing GC
Rss 92.7890625MB
Heap 26.09455108642578MB
executing GC
Rss 92.828125MB
Heap 26.116790771484375MB
executing GC
Rss 92.92578125MB
Heap 26.204917907714844MB
executing GC
Rss 93.2109375MB
Heap 26.21637725830078MB
executing GC
Rss 93.51953125MB
Heap 26.239288330078125MB
executing GC
Rss 93.78515625MB
Heap 26.29064178466797MB
executing GC
Rss 97.98828125MB
Heap 26.005172729492188MB
executing GC
Rss 96.97265625MB
Heap 26.08238983154297MB
executing GC
Rss 97.109375MB
Heap 26.201927185058594MB
executing GC
Rss 97.1953125MB
Heap 26.2103271484375MB
executing GC
Rss 97.234375MB
Heap 26.269744873046875MB
executing GC
Rss 97.2734375MB
Heap 26.291542053222656MB
executing GC
Rss 103.59375MB
Heap 26.630218505859375MB
executing GC
Rss 103.48828125MB
Heap 26.243072509765625MB
executing GC
Rss 103.73046875MB
Heap 26.271713256835938MB
executing GC
Rss 103.91796875MB
Heap 26.36101531982422MB
executing GC
Rss 103.96875MB
Heap 26.360397338867188MB
executing GC
Rss 105.19140625MB
Heap 26.70825958251953MB
executing GC
Rss 105.43359375MB
Heap 26.263877868652344MB
executing GC
Rss 106.56640625MB
Heap 26.525856018066406MB
executing GC
Rss 105.4921875MB
Heap 26.340621948242188MB
executing GC
Rss 106.6328125MB
Heap 26.587791442871094MB
executing GC
Rss 103.9765625MB
Heap 26.361915588378906MB
executing GC
Rss 104.03125MB
Heap 26.393455505371094MB
executing GC
Rss 104.0859375MB
Heap 26.416732788085938MB
executing GC
Rss 104.31640625MB
Heap 26.477127075195312MB
executing GC
Rss 104.37890625MB
Heap 26.502410888671875MB
executing GC
Rss 112.69921875MB
Heap 26.992828369140625MB
executing GC
Rss 107.4140625MB
Heap 26.386489868164062MB
executing GC
Rss 103.51953125MB
Heap 26.232513427734375MB
executing GC
Rss 96.62109375MB
Heap 26.46930694580078MB
executing GC
Rss 95.65625MB
Heap 26.418663024902344MB
executing GC
Rss 95.5390625MB
Heap 26.448448181152344MB
executing GC
Rss 96.3671875MB
Heap 26.43299102783203MB
executing GC
Rss 98.5390625MB
Heap 26.465614318847656MB
executing GC
Rss 98.76953125MB
Heap 26.460464477539062MB
executing GC
Rss 98.76953125MB
Heap 26.45654296875MB
executing GC
Rss 98.76953125MB
Heap 26.460479736328125MB
executing GC
Rss 98.77734375MB
Heap 26.48870849609375MB
executing GC
Rss 98.81640625MB
Heap 26.343666076660156MB
executing GC
Rss 98.8203125MB
Heap 26.4720458984375MB
executing GC
Rss 98.828125MB
Heap 26.50202178955078MB
executing GC
Rss 98.828125MB
Heap 26.470077514648438MB
executing GC
Rss 99.3515625MB
Heap 26.56096649169922MB
executing GC
Rss 96.6640625MB
Heap 26.345123291015625MB
executing GC
Rss 96.48828125MB
Heap 26.475013732910156MB
executing GC
Rss 96.30078125MB
Heap 26.505996704101562MB
executing GC
Rss 96.30078125MB
Heap 26.471572875976562MB
executing GC
Rss 96.80078125MB
Heap 26.525596618652344MB
executing GC
Rss 96.82421875MB
Heap 26.34589385986328MB
executing GC
Rss 96.82421875MB
Heap 26.5614013671875MB
executing GC
Rss 95.484375MB
Heap 26.467361450195312MB
executing GC
Rss 92.94140625MB
Heap 26.49822998046875MB
executing GC
Rss 92.99609375MB
Heap 26.325927734375MB
executing GC
Rss 93.9453125MB
Heap 26.380950927734375MB
executing GC
Rss 93.95703125MB
Heap 26.419105529785156MB
executing GC
Rss 94.00390625MB
Heap 26.426239013671875MB
executing GC
Rss 94.00390625MB
Heap 26.4786376953125MB
executing GC
Rss 94.04296875MB
Heap 26.50086212158203MB
executing GC
Rss 97.0859375MB
Heap 26.699508666992188MB
executing GC
Rss 97.2421875MB
Heap 26.49945068359375MB
executing GC
Rss 98.25MB
Heap 26.54810333251953MB
executing GC
Rss 98.30078125MB
Heap 26.59204864501953MB
executing GC
Rss 98.44140625MB
Heap 26.622085571289062MB
executing GC
Rss 99.73828125MB
Heap 26.751808166503906MB
executing GC
Rss 99.78515625MB
Heap 26.649391174316406MB
executing GC
Rss 99.8359375MB
Heap 26.69732666015625MB
executing GC
Rss 99.91796875MB
Heap 26.726905822753906MB
executing GC
Rss 100.0078125MB
Heap 26.762191772460938MB
executing GC
Rss 100.05078125MB
Heap 26.79560089111328MB
executing GC
Rss 101.1953125MB
Heap 26.924362182617188MB
executing GC
Rss 101.26953125MB
Heap 26.829185485839844MB
executing GC
Rss 101.33203125MB
Heap 26.946868896484375MB
executing GC
Rss 101.375MB
Heap 26.89513397216797MB
executing GC
Rss 101.66015625MB
Heap 26.979278564453125MB
executing GC
Rss 101.671875MB
Heap 26.925331115722656MB
executing GC
Rss 101.6953125MB
Heap 26.93054962158203MB
executing GC
Rss 101.71484375MB
Heap 26.968978881835938MB
executing GC
Rss 101.7578125MB
Heap 26.99884033203125MB
executing GC
Rss 101.76171875MB
Heap 27.03069305419922MB
executing GC
Rss 101.85546875MB
Heap 27.091064453125MB
executing GC
Rss 102.66796875MB
Heap 27.11981201171875MB
executing GC
Rss 104.48828125MB
Heap 27.153945922851562MB
executing GC
Rss 104.63671875MB
Heap 27.19652557373047MB
executing GC
Rss 104.640625MB
Heap 27.2261962890625MB
executing GC
Rss 104.70703125MB
Heap 27.267738342285156MB
executing GC
Rss 104.76953125MB
Heap 27.297080993652344MB
executing GC
Rss 104.81640625MB
Heap 27.33283233642578MB
executing GC
Rss 104.90234375MB
Heap 27.381942749023438MB
executing GC
Rss 112.28515625MB
Heap 27.05865478515625MB
executing GC
Rss 111.28125MB
Heap 27.077552795410156MB
executing GC
Rss 111.37109375MB
Heap 27.105392456054688MB
executing GC
Rss 111.37109375MB
Heap 27.134002685546875MB
executing GC
Rss 111.42578125MB
Heap 27.311500549316406MB
executing GC
Rss 111.47265625MB
Heap 27.214637756347656MB
executing GC
Rss 111.515625MB
Heap 27.24488067626953MB
executing GC
Rss 111.5703125MB
Heap 27.386985778808594MB
executing GC
Rss 111.6484375MB
Heap 27.31822967529297MB
executing GC
Rss 111.7265625MB
Heap 27.434539794921875MB
executing GC
Rss 111.7890625MB
Heap 27.42713165283203MB
executing GC
Rss 111.80859375MB
Heap 27.407394409179688MB
executing GC
Rss 111.859375MB
Heap 27.520591735839844MB
executing GC
Rss 111.9140625MB
Heap 27.52300262451172MB
executing GC
Rss 112.01171875MB
Heap 27.561935424804688MB
executing GC
Rss 112.0625MB
Heap 27.592361450195312MB
executing GC
Rss 112.1171875MB
Heap 27.643463134765625MB
executing GC
Rss 112.26171875MB
Heap 27.66069793701172MB
executing GC
Rss 112.4453125MB
Heap 27.708709716796875MB
executing GC
Rss 112.7421875MB
Heap 27.729949951171875MB
executing GC
Rss 113.2421875MB
Heap 27.764808654785156MB
executing GC
Rss 113.37109375MB
Heap 27.806167602539062MB
executing GC
Rss 113.41796875MB
Heap 27.830886840820312MB
executing GC
Rss 113.52734375MB
Heap 27.865585327148438MB
executing GC
Rss 113.578125MB
Heap 27.908302307128906MB
executing GC
Rss 113.875MB
Heap 27.945175170898438MB
executing GC
Rss 113.96875MB
Heap 27.967071533203125MB
executing GC
Rss 136.2109375MB
Heap 28.475143432617188MB
executing GC
Rss 124.00390625MB
Heap 27.728302001953125MB
executing GC
Rss 123.5625MB
Heap 27.70484161376953MB
executing GC
Rss 123.67578125MB
Heap 27.76201629638672MB
executing GC
Rss 123.6875MB
Heap 27.792037963867188MB
executing GC
Rss 126.70703125MB
Heap 28.269004821777344MB
executing GC
Rss 126.796875MB
Heap 27.821014404296875MB
executing GC
Rss 126.83203125MB
Heap 27.778282165527344MB
executing GC
Rss 127.30859375MB
Heap 27.913681030273438MB
executing GC
Rss 127.33984375MB
Heap 27.84935760498047MB
executing GC
Rss 127.33984375MB
Heap 27.876853942871094MB
executing GC
Rss 127.3828125MB
Heap 27.904441833496094MB
executing GC
Rss 127.4296875MB
Heap 28.08391571044922MB
executing GC
Rss 127.47265625MB
Heap 28.033203125MB
executing GC
Rss 127.5MB
Heap 28.011192321777344MB
executing GC
Rss 127.60546875MB
Heap 28.13018798828125MB
executing GC
Rss 127.65625MB
Heap 28.13433837890625MB
executing GC
Rss 127.703125MB
Heap 28.16474151611328MB
executing GC
Rss 127.95703125MB
Heap 28.209060668945312MB
executing GC
Rss 128.05078125MB
Heap 28.24072265625MB
executing GC
Rss 128.1015625MB
Heap 28.281822204589844MB
executing GC
Rss 128.15625MB
Heap 28.313385009765625MB
executing GC
Rss 128.20703125MB
Heap 28.34642791748047MB
executing GC
Rss 128.30859375MB
Heap 28.388504028320312MB
executing GC
Rss 128.37109375MB
Heap 28.419166564941406MB
executing GC
Rss 128.421875MB
Heap 28.448715209960938MB
executing GC
Rss 149.0390625MB
Heap 28.952102661132812MB
executing GC
Rss 141.37890625MB
Heap 28.280860900878906MB
executing GC
Rss 140.359375MB
Heap 28.267410278320312MB
executing GC
Rss 141.26953125MB
Heap 28.46070098876953MB
executing GC
Rss 141.31640625MB
Heap 28.303802490234375MB
executing GC
Rss 142.50390625MB
Heap 28.510887145996094MB
executing GC
Rss 142.50390625MB
Heap 28.336219787597656MB
executing GC
Rss 142.515625MB
Heap 28.449417114257812MB
executing GC
Rss 142.5625MB
Heap 28.401321411132812MB
executing GC
Rss 142.5625MB
Heap 28.52734375MB
executing GC
Rss 142.625MB
Heap 28.47846221923828MB
executing GC
Rss 142.69921875MB
Heap 28.59270477294922MB
executing GC
Rss 142.7421875MB
Heap 28.58769989013672MB
executing GC
Rss 142.8046875MB
Heap 28.619224548339844MB
executing GC
Rss 142.89453125MB
Heap 28.667266845703125MB
executing GC
Rss 142.9453125MB
Heap 28.700279235839844MB
executing GC
Rss 142.98828125MB
Heap 28.726028442382812MB
executing GC
Rss 143.0390625MB
Heap 28.75550079345703MB
executing GC
Rss 143.09375MB
Heap 28.801925659179688MB
executing GC
Rss 143.140625MB
Heap 28.829612731933594MB
executing GC
Rss 143.18359375MB
Heap 28.8642578125MB
executing GC
Rss 143.22265625MB
Heap 28.89765167236328MB
executing GC
Rss 103.3046875MB
Heap 26.35546112060547MB
executing GC
Rss 100.703125MB
Heap 26.526878356933594MB
executing GC

if i keep reload pressed, memory usage grow fast, but then if i wait a bit, and do a reload, the GC is able to clean stuff. not completely, something is still leaking

@asturur
Copy link
Member

asturur commented Jul 14, 2018

also this code will probably crash on a browser.

@asturur
Copy link
Member

asturur commented Jul 14, 2018

image

I added more cleanups, while it does not go back to the the original consumptions, is still recoveing. from 512 to 92

@asturur
Copy link
Member

asturur commented Jul 14, 2018

image

after a bit of rest, it cleaned more chunks an went back to 76, so maybe is not leaking substantial stuff anymore.

But if you plan to handle many request concurrently, with JSON with many images and long async loadings, you may break the 1.5G limit easily

@0ro
Copy link
Author

0ro commented Jul 14, 2018

@asturur you did a great job. I didn't know that I could access HTMLImageElementImpl and I just tried adding to the Image.dispose () also has this code.

['_originalElement', '_element', '_filteredEl'].forEach((function(key) {
  var impl = fabric.jsdomImplForWrapper(this[key]);
  if (impl && impl._image) {
    impl._currentSrc = null;
    impl._attributes = null;
    impl._classList = null;
    impl._image = null;
    impl._eventListeners = null;
    this[key] = undefined;
  }
}).bind(this))

It may look redundant and paranoid, but it's working.
And after 500 requests with my JSON with Image I saw this

Rss 245.16015625MB
Heap 36.9376220703125MB
executing GC

Without your dispose code I saw this

Rss 889.22265625MB
Heap 37.885032653808594MB
executing GC

We are going in the right direction. I think I still need to do dispose for canvas to clean up HTMLCanvasElementImpl. How to access it?

@0ro
Copy link
Author

0ro commented Jul 16, 2018

@asturur After each request, the HTMLImageElementImpl remains in the heap
image
image

@asturur
Copy link
Member

asturur commented Jul 16, 2018 via email

@0ro
Copy link
Author

0ro commented Jul 16, 2018

the heap grow very little, is that a reference and the big part is then in
the rss?

Probably Yes.

how do you inspect that?

Using the --inspect in node flag and chrome://inspect/
image

in the memory tab, I took a snapshot before and after request and then compared them

image

@0ro
Copy link
Author

0ro commented Jul 16, 2018

And I use supertest to generate requests

const request = require(`supertest`);
const PORT = 8124;
const HOSTNAME = `127.0.0.1`;
const app = `http://${HOSTNAME}:${PORT}`;
const fs = require(`fs`);

let promises = [];

for (let i = 0; i < 100; i++) {
  promises.push(request(app)
      .post(`/`)
      .type(`form`)
      .send(``)
      .expect(200)
      .then((res)=> {
        fs.writeFile(`bgs/bg${i}.png`, res.body, (err) => {
          if (err) {
            throw err;
          }
        });
      }));
}

Promise.all(promises).then(() => {
  console.log(`All`);
});

@stewartmatheson
Copy link

+1 to this.

@0ro would you be able to paste more details on your solution? Might be nice for others coming to this thread. I have tried the following but perhaps I don't understand how you have applied your fix. Thanks to both you and @asturur on your awesome work with this issue.

fabric.Image.dispose = function() {
    console.log('Calling dispose for image');
    var backend = fabric.filterBackend;
    if (backend && backend.evictCachesForKey) {
        backend.evictCachesForKey(this.cacheKey);
        backend.evictCachesForKey(this.cacheKey + '_filtered');
    }
    this._originalElement = undefined;
    this._element = undefined;
    this._filteredEl = undefined;

    ['_originalElement', '_element', '_filteredEl'].forEach((function(key) {
        var impl = fabric.jsdomImplForWrapper(this[key]);
        if (impl && impl._image) {
            impl._currentSrc = null;
            impl._attributes = null;
            impl._classList = null;
            impl._image = null;
            impl._eventListeners = null;
            this[key] = undefined;
        }
    }).bind(this))
}

@0ro
Copy link
Author

0ro commented Jul 17, 2018

@stewartmatheson The issue is not fix. memory is still leaks, but a little slower than before. HTMLImageElementImpl still remains in memory.

@stewartmatheson
Copy link

I’m still seeing a huge increase in memory though so I don’t think the code I have posted is working correctly. Can you post your solution that applies the code correctly?

@0ro
Copy link
Author

0ro commented Jul 17, 2018

@stewartmatheson Your code is correct, but I don't know how remove HTMLImageElementImp. There is still a link to it in memory. We clear its fields, but it remains in memory. We should try to remove its from memory.

@0ro
Copy link
Author

0ro commented Jul 17, 2018

@asturur You know what to do with this?
image

@0ro
Copy link
Author

0ro commented Jul 17, 2018

I tried to remove the src attribute of the image in order to clear it but got this error jsdom/jsdom#2150

image

@stewartmatheson
Copy link

@0ro How do you figure that its the HTMLImageElementImpl that is causing the issue? I have setup the debugger taken two heap snapshots and compared the results and I have noticed that there are a number of objects that keep getting allocated. At this point I suspect that there is more within JSDOM to cleanup rather than just images. Does fabric somehow keep a hold of JSDOM internally?

screen shot 2018-07-18 at 12 10 30 pm

@asturur
Copy link
Member

asturur commented Jul 18, 2018

Hi, i just wanted to say that we should anyway verify what is the suggested way to get rid of JSDOM objects before do manual clenaning, it looks weird to me that we are the only one with this problem.

I will open a pr with the changes i experimented with as soon as i have a bit of time.

With this code the memory will increase anyway, what we can ask is that after all the async process is done, another forced GC will eventually erase everything.

@stewartmatheson
Copy link

stewartmatheson commented Jul 18, 2018

@asturur It looks like jsdom have had issues in the past that match up to our use case. jsdom/jsdom#784 contains a comment in there that suggests that window.close() will clear the scripts and allow GC to run correctly. I suspect this might help us in our situation however it would require us to change the way fabric gets it's document. I have noticed in HEADER.js we do

if (typeof document !== 'undefined' && typeof window !== 'undefined') {
  fabric.document = document;
  fabric.window = window;
}
else {
  // assume we're running under node.js when document/window are not present
  fabric.document = require('jsdom')
    .jsdom(
      decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'),
      { features: {
        FetchExternalResources: ['img']
      }
      });
  fabric.jsdomImplForWrapper = require('jsdom/lib/jsdom/living/generated/utils').implForWrapper;
  fabric.nodeCanvas = require('jsdom/lib/jsdom/utils').Canvas;
  fabric.window = fabric.document.defaultView;
  DOMParser = require('xmldom').DOMParser;
}

which means that the jsdom will always be a singleton instance. Fair enough as we don't have to pay the init cost each time however it's not getting GC'd. I think @0ro has hinted at this but like they said draws after calling close would not work as there is a singleton instance of jsdom. Is it reasonable for us to set fabric.window, fabric.nodeCanvas and fabric.document after cleaning the old jsdom instance with window.close() Would the overhead be too much? Is there more in fabric that would cause other issues?

@stewartmatheson
Copy link

stewartmatheson commented Jul 18, 2018

OK so I have implemented a fix for the canvas loading. I am able to call window.close() and then render again after assigning a new jsdom. However it looks like there is another issue here. jsdom/jsdom#1665 is open and it speaks of the JSDOM window hanging around after window.close() has been called. At this point the work around solution of re-creating the jsdom instance will not work due to this issue. I'm back at square one now. I think the best thing to do is to understand why canvas.dispose() does not clean up the Images as @0ro has pointed out. Thoughts @asturur ?

@asturur
Copy link
Member

asturur commented Jul 18, 2018 via email

@0ro
Copy link
Author

0ro commented Jul 23, 2018

@asturur how are you doing with this problem? can I help you with something? I've spent a lot of time researching garbage heaps, but found nothing suspicious except HTMLImageElementImpl. If you add images to the canvas using fabric.Image.FromURL then pile grows even faster.

@asturur
Copy link
Member

asturur commented Aug 5, 2018

https://github.com/fabricjs/fabric.js/pull/5142/files

I made this PR that while it does not solve 100%, it stil better than the original situation by far.

@0ro
Copy link
Author

0ro commented Aug 7, 2018

Thank you

@sameer1001
Copy link

I am facing the exact same issue with "fabric": "^4.1.0"

For me with every render, RSS increases by 30 MB.

@asturur @0ro is this an acknowledged issue with fabric library? Do we have any workaround for this?

@asturur
Copy link
Member

asturur commented Dec 19, 2020

i m not sure we have a reproducible
case that is 30
meg each render. Do you have one?

@sameer1001
Copy link

yes, I am trying with an image with 18 layers.

15 of them are image layer. I guess more the image layers, RSS increases faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants