Hi, I’m hoping someone can help shed some light on what might be happening here.
I have a large memory leak in my app that I think I’ve narrowed down to a mouse-down handler which rerenders the sub component tree. If I use on-mouse-down then I get a leak, but the same code with a on-click handler does not leak. I think the whole sub tree is being kept around, but it’s very noticeable if the arguments to one of the sub functions is a very large object.
I am on reagent 1.1.1
The code below will leak about 100mb every time you click back and forth between the components
(defn create-large-object []
(into-array
(mapv
(fn [i]
{:id i
:s (str i)})
(range 0 500000))))
(defn leak-memeory-sub-comp [props]
[:div "I'm leaking"])
(defn leak-memory-comp []
(r/with-let [large-object (create-large-object)]
[leak-memeory-sub-comp {:large-object large-object}]))
(defn memory-leak-main []
(r/with-let [*focused? (r/atom false)]
[:div
{:style {:padding-top "24px"
:text-align "center"
:font-size "36px"}}
[:div
;; on-mouse-down causes it to leak memory from the sub components argv
{:on-mouse-down #(swap! *focused? not)}
;; but on-click does not
;; {:on-click #(swap! *focused? not)}
(if @*focused?
[leak-memory-comp]
[:div "click me!"])]]))100MB? Sounds a lot for half a million of clojure maps how do you know it's a memory leak?
By leak I mean the memory keeps increasing every time I click the div twice. forcing garbage collection does not clear the memory I would think that, if “click me” is showing that the large object shouldn’t be in memory, but it is. Here is the object in a heap snapshot
possibly the 100MB could be because keywords are not interned unless it’s advanced?
I see, that's interesting find. Would be good to verify that it's Reagent's problem, not React itself.
I don't know Reagent's internals well, but I think the problem might be here https://github.com/search?q=repo%3Areagent-project%2Freagent%20generation&type=code and if it is, then it should be dev only issue, so I'd also check of the same leak occurs in prod builds
on the other hand generation is just a number, does the leak happen when using form-2 components instead of with-let?
In the production build of my app I have the same memory leak, but there is a lot more going on so could be from something else, I will check form-2 and try a production build
it seems to still leak with form-2
is that an isolated example? I'm interested to debug it as well
yeah that example should leak for you, I am not in a fresh project but I pasted it at the top of my component tree
It is also leaking in a production build for me
interesting that the on-click handler fixes it because in my app, the leak actually happens without clicking on anything, I trigger the leak from key downs, still haven’t connected if the leak in my example and my app are the same
I will make a fresh project so this is easier to reproduce
Hmm, I'm not seeing a leak reagent 1.1.1, react 18
oh I am on react 17
is that Chrome browser of Electron?
I will try this in a fresh project and see if i can reproduce
chrome
react “^17.0.2”
> oh I am on react 17 what is the exact React version?
thanks
hehe, seems like it is React's issue
I get it in v17 but not in v18
oh!
fun
maybe not React's fault, could be also something with how Reagent interacts with React, since v18 is a bit different
it should be safe to upgrade to v18 btw
aren’t there breaking changes? I have a massive list of dependencies that haven’t been upgraded and extensions built on top of my app the rely on react 17 lol
in v18 if you still use rdom/render instead of React Root API, React will behave as if it's v17, with minor differences
but I'd keep an eye on those and do a bit of manual testing to make sure nothing weird is going on
At the time at Pitch upgrading from v17 to v18 in "legacy" mode went w/o any major problems, it's upgrading to concurrent rendering mode (using react root) that makes a big difference
Is there a replacement for dom-node? I think we use this a lot
well, if it’s the only way to solve this leak then I will have to upgrade
not sure but I don't think those APIs were actually removed
found this in v18.0.0 changelog
that makes it sound like there is also an issue in reagent?
yep
Thanks for your help!
I will open an issue in reagent
For anyone else that comes across this thread, here is a repo you can reproduce it with https://github.com/panterarocks49/reagent-memory-leak
ok, tested in plain React v17, still leaking, pretty sure now that it's React's problem
it does GC leaked values at some point though ,just that when using JS objects 500k of them is only 10-15MB of memory so it's not very visible
but when using Clojure maps, for some reason GC won't kick in, still waiting
sorry, do you mean it does GC javascript maps but not clojure maps?
could you post the react code?
> sorry, do you mean it does GC javascript maps but not clojure maps? correct
(ns example.core
(:require ["react-dom" :as rdom]
[react]))
(def $ react/createElement)
(defn create-large-object []
(into-array
(mapv
(fn [i]
#js {:id i
:s (str i)})
(range 0 500000))))
(defn leak-memeory-sub-comp [props]
($ "div" nil "I'm leaking"))
(defn leak-memory-comp []
(let [large-object (create-large-object)]
($ leak-memeory-sub-comp #js {:argv {:largeObject large-object}})))
(defn memory-leak-main []
(let [[focused? set-focused] (react/useState false)]
($ "div"
#js {:style #js {:paddingTop "24px"
:textAlign "center"
:fontAize "36px"}}
($ "div"
;; on-mouse-down causes it to leak memory from the sub components argv
#js {:onMouseDown #(set-focused not)}
;; but on-click does not
;; {:on-click #(swap! *focused? not)}
(if focused?
($ leak-memory-comp)
($ "div" nil "click me!"))))))
(.render rdom
($ memory-leak-main)
(js/document.getElementById "root"))the thing that hangs in memory is something called memoizedProps, that's basically current props tracked by react internally
the field is attached to fiber instance that's referenced in a DOM node that corresponds to a component that takes that big value
so it's not with-let, but just the fact that values are passed in props
ahh, I wonder why it is cleaned up correctly with on-click as opposed to mouse down
I can confirm react 18 solves the issue in my full app.. hopefully upgrading is not too painful