2

I'm trying to figure out whether a memory leak can be created by an async function that never resumes. For example:

  function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  function Test() {
    this.arr = [];

    this.alloc = async function() {
      for (i = 0; i < 300000; i++) {
        this.arr.push(document.createElement('div'));
      }
      await timeout(10000);
      alert('done waiting ' + this.arr.length); // outputs 300000
    };
  };

  var test = new Test();

  function leak() {
    test.alloc();
    test = null;
    window.gc(); // Running chrome with expose-gc flag
    // Snapshotting memory here shows divs have been deallocated
  }

Using Chrome's memory tools, I grabbed a snapshot when this code finishes executing. I would have expected to see 300000 HTMLDivElements still allocated, but lo and behold - the memory seems "freed". What's strange is if I try to access the array contents they are still there. Can anyone explain this phenomenon?

Dandan
  • 519
  • 2
  • 9
  • 1
    Where is `expose-gc` flag documented? – guest271314 Jul 15 '17 at 02:00
  • If gc acts weird and is affected by `console.log` (or `alert`) statement being either present or not, my guess would be that an `await` enables the same [scope optimisations that closures get](https://stackoverflow.com/questions/28388530/why-does-chrome-debugger-think-closed-local-variable-is-undefined). – Bergi Jul 17 '17 at 20:48

1 Answers1

1

If I replace someForeverPromise with a timer and then try to access the array contents they are still there

Your someForeverPromise itself was garbage-collected, and with it all the callbacks that were still waiting for it - including the resumption of the async function.

You can use

const foreverPendingPromise = new Promise(resolve => {
    global.reference = resolve;
});

where the global reference keeps up a way to resolve the promise to prevent the callbacks from being garbage-collected. (You also should make sure that no-one accidentally calls reference to ensure it stays pending, but I'll leave that as an exercise to the reader).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • That's odd though, because if I replace the promise with a timer and add an alert it still shows up. – Dandan Jul 17 '17 at 17:40
  • @Dandan A timer does keep a global reference to its callback (`resolve`?) as well – Bergi Jul 17 '17 at 17:48
  • I used the following implementation: function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } Now if I await timeout(10000) then profiler shows memory as freed but I still get the alert. – Dandan Jul 17 '17 at 18:56
  • @Dandan Didn't you state in your original question that when you use that timeout, the array data is still there, but when you used a never resolving promise it's freed? – Bergi Jul 17 '17 at 20:46