Fork me on GitHub
#re-frame
<
2023-08-09
>
Dr. Moltu17:08:47

Hello. So I was browsing re-frame and this came to me: Say that some events are queued for execution and some are executing. Also say that we have an input field with (dispatch-sync ...) and we started spamming/typing text in that input field while other events are executing async. If we mutate the app-db with (dispatch-sync) the moment an event was processing the previous app-db value before that dispatch-sync and the event finally reaches the db-fx that mutates the app-db, the app-db was already altered by dispatch-sync. Isn't that a race condition? Thanks!

p-himik17:08:59

It's a race condition only if something is racing for something. dispatch-sync is a means to run events out-of-band. What you do with it is up to you, some approaches will result in race conditions but some will not. I am yet to see a proper race condition.

Dr. Moltu19:08:13

The way I see it is like a swap in atoms but on a larger scale, you start with certain value and use it to do some work, but the moment you're ready to swap the old value with a new one, the old is already changed (aka not atomic). Do you see this as a problem? yes I get that it's out-of-band but it can cause issues and it's never mentioned at all. IHMO, the right thing would be for it to be atomic.

Dr. Moltu19:08:53

Maybe check if (compare-and-set old-db new-db) else re-dispatch the event?

p-himik19:08:13

As I sad - it can be a problem. > the right thing would be for it to be atomic What do you mean by "it" here? > Maybe check if (compare-and-set old-db new-db) else re-dispatch the event? Anything that's async is a potential source for race conditions. Probably the most frequent example in my own work - a user opens a dialog, data fetching is triggered, the user closes the dialog before the data arrives, then the data arrives and you have a problem. Somewhat easily solved by a manual sort of compare-and-set on a specific value in the DB (e.g. :dialog-opened-at timestamp). And there's at least one library that solves it on a more systemic level by introducing a concept of epochs in addition to state machines. A general advice that helps pretty much avoid any race conditions when using dispatch-sync is to never use it with "complex" events. IMO it should be used only with reg-event-db or something that's not too far from it. Of course, in theory there's still room for race conditions given that the same value can be written by different events. To further alleviate that, dispatch-sync should only be used with user input. I cannot think of a reason where you'd have to use it for something else (apart from, maybe, DB initialization).

Dr. Moltu19:08:14

I mean by "it": from the moment the first event in the queue starts to execute to the moment the db effect/fx is called to mutate the app-db. I totally agree with you that dispatch-sync is better used with light event handlers, and I'm not saying to use it anywhere else. but even in that case, as you said, there is still a chance for race. For example, the timer app, ticks every second by (dispatching time). If you have an input text in that UI with dispatch-sync and reg-event-db, as you type the cursor can fall behind, it's rare but it can happen if that makes sense?

p-himik19:08:08

Not sure I follow. If you're frequently changing a user-editable input field programmatically, then race conditions in this case are trumped by poor UX. I'm acknowledging that there is a problem. But I sincerely doubt that it's in any way pronounced and worth fixing, potentially breaking some other stuff that didn't expect for event handlers to be re-run.

Dr. Moltu19:08:20

Ok so I'm not changing a user-editable input field programmatically, but via typing in the input filed manually and that input filed is subbed to app-db ofc. the thing is app-db gets updated by, say for example, a timer every second, then if you type long enough, the timer event will encounter that old-db changed problem, and will change the app-db with the old-db value it got first while your input field expects the new one since it finished first and updated the app-db before timer did. I hope I clarified I know it's confusing

Dr. Moltu19:08:54

also, at the top of my head, I don't think rerunning an event is harmful since the first effect is db and other effects will not get to execute so no one will know?

p-himik19:08:02

Assuming I understand you correctly, events are not handled when another event is being handled. While the code is async, it's not async within handlers, and JS is single-threaded. So while a timer event is being handled, there can be no change to app-db.

p-himik19:08:59

> also, at the top of my head, I don't think rerunning an event is harmful Handlers should be pure functions. But not all people use them that way. There are also co-effects that can become different on re-dispatching some event.

💯 2
Dr. Moltu19:08:54

"So while a timer event is being handled, there can be no change to app-db." so you're saying one event has to finish before another starts regardless if it's dispatch or dispatch-sync?

p-himik20:08:13

Yes. In JS you cannot interrupt and resume a function, at least not without using async.

Dr. Moltu20:08:53

ok but say the re-frame queue has another event waiting, which one goes first that event or dispatch-sync event? and who decides who?

p-himik20:08:45

The latter. dispatch-sync directly handles the event, completely circumventing the queue.

Dr. Moltu20:08:41

Ok just to recap and be sure: JS is the guarantee that this will never happen.

Dr. Moltu20:08:38

and if it wasn't single threaded, the problem would occur.

Dr. Moltu20:08:26

Ok good stuff. Thank you very much my friend 😀.

👍 2