Any thoughts on this PR? Mergeable? https://github.com/day8/re-frame/pull/810 https://github.com/day8/re-frame/issues/809 https://github.com/lowecg/re-frame-issue-809
Oh wow, a non-CLJS usage of re-frame in the wild that's not for tests! First time I see something like that. :)
My takeaways so far:
• calling dispatch actually pushes an event (vector) onto a https://github.com/day8/re-frame/blob/f654f42430ac0bad9ad43688ebc9d712fb26b4e6/src/re_frame/router.cljc#L222
• when re-frame pulls an event from the queue (to call the event handler), that's called a https://github.com/day8/re-frame/blob/f654f42430ac0bad9ad43688ebc9d712fb26b4e6/src/re_frame/router.cljc#L139
• this queue https://github.com/day8/re-frame/blob/f654f42430ac0bad9ad43688ebc9d712fb26b4e6/src/re_frame/router.cljc#L31, leaving time for stuff like draw calls & (non-re-frame) event loops
• when running re-frame on the JVM, ticks are https://github.com/day8/re-frame/blob/f654f42430ac0bad9ad43688ebc9d712fb26b4e6/src/re_frame/interop.clj#L31.
• the executor needs an explicit shutdown call. Otherwise, https://github.com/lowecg/re-frame-issue-809.
• some people want to https://github.com/oliyh/re-graph/issues/75#issuecomment-2478108942. What exactly does that mean?
> • when running re-frame on the JVM, ticks are https://github.com/day8/re-frame/blob/f654f42430ac0bad9ad43688ebc9d712fb26b4e6/src/re_frame/interop.clj#L31. Why? Why not just handle them on the main thread, the same was as it happens in thread-less JS?
re-frame.interop/next-tick in cljs isn't exactly "main thread" behavior, since it uses goog.async.nextTick
I suppose the executor gives us something comparable, providing a rhythm of execution which is somehow nicer for other threads. https://stackoverflow.com/questions/50076815/singlethreadexecutor-vs-plain-thread
It's still right there on the main thread in JS, because there's no other thread.
Nothing else is going on when a handler fed to nextTick is running. Except for web workers and some other low-level stuff that re-frame doesn't use.
And I'm not comparing an executor to an explicit single thread (in fact, re-frame used to use a single thread at the start). I'm comparing it to not using any extra threads at all.
In other words, what are the downsides of turning next-tick into
(defn next-tick [f]
(f)
nil)
?
(And why does the current impl use some args when there are no args?..)If I limit my thinking to JS only, there would be two downsides that I can think of:
• UI hanging because nothing would give it a chance to render, since rendering is also done on the main thread
• The order of (dispatch ...) (dispatch-sync ...) would become reversed
The former is not applicable to Java.
The latter is not applicable to the way things are currently implemented. And there are no explicit guarantees about the order of those two things anyway, as far as I'm aware.
> don't like the idea of calling f directly because it would mean that in your tests you would see synchronous behaviour that in the real-world would be [a]synchronous.
I agree in principle but I disagree that it's of any use for tests. JVM and JS are vastly different, testing re-frame on the former can in no way prove that the same tests would end in the same result on JS, and vice versa.
Threads are not an async queue that is JS.
And given that the issue was created not in the context of tests but in the context of a real app (which makes the PR 183 a bit funny with the "not the goal" opening :) ), I'd say preferring to make things simpler for regular apps as opposed to requiring them using some custom CLJ-only API makes sense.
sounds like a good PR.
but for now, the executor is in re-frame stable. we should probably just support it. and the current PR is pretty small.
By merging the current PR, you'll expand the API thus cementing the executor in place. The fact that something is stable doesn't mean that it should remain there forever.
yeah. if removing the executor solves the repro, then maybe it's fine. https://github.com/lowecg/re-frame-issue-809
the "concurrency issues" mentioned here might be related to: https://github.com/day8/re-frame/issues/469
although that issue seems to be caused by the executor 😛
Exactly! So it seems that not just the executor but also the work done in https://github.com/day8/re-frame/pull/471/ could be revisited. It even states my concern in the OP there: > • it works fine in cljs because that's single-threaded > • jvm clojure interop creates a single threaded executor, but that coexists with the main thread for a total thread count of 2 So it doesn't make sense to use another thread to be "closer to CLJS".
> • The order of (dispatch ...) (dispatch-sync ...) would become reversed
>
> The latter is not applicable to the way things are currently implemented. And there are no explicit guarantees about the order of those two things anyway, as far as I'm aware
Could you describe this more? I'm not sure how to imagine this happening in practice.
What I meant is that if you have code like this
(dispatch [:a])
(dispatch-sync [:b])
then :b would be handled earlier than :a because :a would be on the queue.
On the CLJ side, if you get rid of the extra thread, it would :a would be handled first. So, the order is reversed.
But with an extra thread, there's no guaranteed order altogether - anything goes.interesting
But as far as I recall, re-frame docs don't make any explicit guarantees apart from "`dispatch-sync` handles an event right at this moment".
I wonder if there's a way to actually get the same behavior
Nothing that would be worth it.