2

I'm updating an opensource project of mine, an object-changes notifier that uses RxJS. To achieve this, I'm using a Weakmap to keep tracking of object position and "property chain" (e.g. "a.b.c", ...). I'm setting as Weakmap keys ES6 Proxies, all with the same handlers.

I've created a wrapper class that, in its constructor iterates the source-object and returns a "Proxy-chain" (i.e. if a property is an object, it becomes a Proxy, and so on). To iterate the objects, it uses the function below.

The problem I'm experiencing seems to be a memory-leak (I suppose) that I'm not able to understand where it comes from. The problem happens also without using the class, so I think that might be related to this function somehow.

function createProxyChain(sourceObject, handlers, all, parents) {
    const descriptors = Object.getOwnPropertyDescriptors(sourceObject);
    const targetObjectKeys = Object.keys(descriptors);
    for (let i = targetObjectKeys.length, prop; prop = targetObjectKeys[--i];) {
        if (sourceObject[prop] && typeof sourceObject[prop] === "object") {
            const parentChains = parents && parents.map(c => `${c}.${prop}`) || [prop];
            const proxyChain = createProxyChain(sourceObject[prop], handlers, all, parentChains);
            descriptors[prop].value = proxyChain;
            all.set(proxyChain, null);
        }
    }
    const chain = Object.create(Object.getPrototypeOf(sourceObject) || {}, descriptors);
    const proxiedChain = new Proxy(chain, handlers);

    return proxiedChain;
}

And to execute it:

var wk = new WeakMap();
var myObject = {
    a: 1,
    b: {
        c: 2,
        d: {
            e: 3,
            f: {
                g: 4,
                h: {
                    i: 5,
                    j: {
                        k: 6,
                        l: {
                            m: 7,
                            n: [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
                        }
                    }
                }
            }
        }
    }
}

createProxyChain(myObject, {}, wk);

Once the function ends, I'm getting my WeakMap with about 10 keys. If I force Garbage Collector run in Chrome/Node through flags and (window|global).gc([true]), I expect them to be removed, shouldn't they?

Instead, the Weakmap remains filled and I can see it by logging the Weakmap in the console. In a big test I created with a lot of setting actions it might reach over 100 keys. Is this behavior correct, or there's actually a memory-leak?

It seems to happen even if I save not-proxy elements in the object instead of proxy ones. It happens also if I do myObject = null (to remove any possible reference).

Thank you very much.

  • See also https://stackoverflow.com/q/49841096/1048572 – Bergi Feb 06 '20 at 12:22
  • Already saw that before opening this question. As I explained in the answer, the problem was the fact that writing in the REPL line-by-line, the Weakmap wasn't getting emptied. Executing a whole chunk all together (as in the picture) was making this working. Thank you anyway @Bergi – Alexander Cerutti Feb 07 '20 at 13:34
  • What's the project? I added this feature to bindinator.js (which already makes extensive use of "paths") without using WeakMap at all, and am now contemplating using WeakMap to make it more performant (it's a bit slow for virtual tables with 1M rows of proxied objects). – podperson Feb 25 '21 at 22:41
  • @podperson https://github.com/alexandercerutti/roxe – Alexander Cerutti Feb 26 '21 at 11:48

1 Answers1

0

It seemed like the problem was not a real problem. In fact, I was using the console to test this. I figured out that, also reading somewhere on StackOverflow, that using console does not allow some object to be garbage collected. This happens both on V8 and SpiderMonkey.

Executing the code chunks above all together, also with this:

myObject = null;
(window || global).gc(true); // Valid only for V8

creates a Weakmap and successfully empties it.

So what I did was to execute line-by-line, close the console and reopen it. I printed the WeakMap content and it wasn't available anymore. (This, both on Chrome and Firefox) It might be needed to open and close different times to see the garbage collection done.

To force the garbage collection on Firefox, I opened a new tab on about:memory and tried to click the button GC.