kieler / elkjs

ELK's layout algorithms for JavaScript
Other
1.82k stars 97 forks source link

Memory leak in Node runtime #148

Open cpepin opened 3 years ago

cpepin commented 3 years ago

Version: 0.7.1

I've been seeing some memory leaks from calls to elk.layout in my Apollo node server. For reference, here is the heap trace:

Screen Shot 2021-09-10 at 12 55 34 PM

It looks like the elk-worker.js file is keeping a bunch of Array instances around in memory on subsequent calls to my resolver function which uses elk.layout to generate x/y coordinates for a graph I'm rendering on the client. I wasn't sure if this was something being observed by folks using the library in the browser, but it does appear to happen consistently in the node runtime. Could also have something to do with the interoperability between the worker code, and Apollo/Express.

FWIW, my resolver is closing over the instantiation of ELK, and additionally I've tried creating deep clones of any data that the layout generates that I transform.

something like...

myResolver = async () => {
  // get my layout
  const layout = await new Elk().layout({ // .. options });

  // do some additional transformations
  return { something: cloneDeep(layout.children), something2: cloneDeep(layout.edges) };
}

Let me know if there is anything else I can provide that might help with this. Thanks!

uruuru commented 3 years ago

From the presence of DCPolynomio I conclude that you're configuring some sort of post compaction or are using the disco layouter?

You could check if you observe the memory leak without this configuration. If not, it could be a bug in that part of the code. Afaik it hasn't been used that extensively, hence it could well be that it hasn't been noticed so far.

cpepin commented 3 years ago

@uruuru We are indeed using the disco layout algo. I'll get another trace using layered and see if the problem persists.

cpepin commented 3 years ago

@uruuru so for comparison I tried out the layered algorithm, and I'm seeing the same issues. Ultimately, it looks like the instance of FakeWorker() is holding onto several instances of ArrayList (and possibly other objects, It was a lot to even dig that up).

Screen Shot 2021-09-14 at 4 29 25 PM

These are the recurring ArrayList instances. The retained size here is deceptive, since the majority of it lies in the native array that is backing this data structure. Note: It looks like one of these is some sort of configuration list, as I see a specific set of these for each run of layout hanging out in the heap.

Screen Shot 2021-09-14 at 4 28 47 PM Screen Shot 2021-09-14 at 4 28 33 PM
uruuru commented 3 years ago

Thanks for following this up.

Parts of code that may be relevant are the following: https://github.com/kieler/elkjs/blob/acd102f444031cfb501901e2327cde2637de10b8/src/java/org/eclipse/elk/js/ElkJs.java#L72-L75

And the Java part:

https://github.com/kieler/elkjs/blob/acd102f444031cfb501901e2327cde2637de10b8/src/java/org/eclipse/elk/js/ElkJs.java#L164 https://github.com/kieler/elkjs/blob/acd102f444031cfb501901e2327cde2637de10b8/src/java/org/eclipse/elk/js/ElkJs.java#L191 https://github.com/kieler/elkjs/blob/acd102f444031cfb501901e2327cde2637de10b8/src/java/org/eclipse/elk/js/ElkJs.java#L204

If, after several layout invokations, lots of GraphInfoHolder etc. remain in memory, it feels like elkGraph hasn't been cleaned properly. Those leftovers are instantiated internally during the layout process but are not transferred to the input graph.