I am trying to see if it is possible to use a subscription in an event handler without creating a memory leak, while also avoiding the need to dispose of reactions created by re-frame.core/subscribe
like is done in this example: https://github.com/den1k/re-frame-utils/blob/master/src/vimsical/re_frame/cofx/inject.cljc
Here is my implementation using coeffects. I am not super familiar with the re-frame code base so I was wondering if anyone could foresee any issues with what I'm trying to do.
(rf/reg-cofx
::sub
(fn [{:keys [db] :as cofx} [operation :as query-v]]
(if-let [cached-reaction (re-frame.subs/cache-lookup query-v)]
(assoc cofx operation @cached-reaction)
(if-let [sub-handler (re-frame.registrar/get-handler :sub operation true)]
(let [reaction (sub-handler db query-v)]
(assoc cofx operation @reaction))
cofx))))
The basic idea is to check the subscription cache and take advantage of it if I can, and if not just run the subscription directly. Does anyone have thoughts on this? Is avoiding the dispose!
function a worthy effort?The sub mentioned by query-v
can have other subs as its input signals. In that case, it will use subscribe
and thus will create a memory leak.
Apart from that, I don't see any pitfalls. Well, except that you're using private API.
Would that mean the memory leak issue is also present in the code I referenced here: https://github.com/den1k/re-frame-utils/blob/master/src/vimsical/re_frame/cofx/inject.cljc?
If so, does that make dispose!
unavoidable?
And I'm alright with the risk of using a private API, as it stands now I'm not using this anywhere. Glad you pointed that out though.
That code calls dispose!
, and I believe it will destroy all signal subs if they're not used in any view or other sub because they would not have any watches after the main sub is destroyed.
But I may be wrong, so you should check that.
> If so, does that make dispose! unavoidable? With the way subs are written now in re-frame, yes.
That's super helpful, I'll investigate. Thanks again for answering my questions!
You can create your own subs mechanism that would work on top of re-frame. The only thing required for you to be able to run subs in any places is to be able to extract the unwrapped sub function, along with all its input signals. This way, it would be possible to traverse the sub tree and call each function (and maybe looking in the cache each time just in case the value is already there).
Although, cache lookups will create a possibility of race conditions. Or maybe that's a wrong term in this case.
If you e.g. (let [db (assoc db :a 1)] ...)
and feed that new db
to such a raw sub function, any cache lookups will use the old db, without {:a 1}
. Tricky stuff.
That seems do-able for handling input signals. Where might the invalid cache scenario come up? I wouldn't plan on making changes to the DB anywhere until the subscription co-effect is complete.
Another coeffect, for example.
Oh I see. That does make sense. You've given me a lot to think about then. I'm unsure if I can avoid that outdated cache issue with the data I am given.