Fork me on GitHub
#clojurescript
<
2021-09-26
>
thheller05:09:28

@borkdude there is no such thing as "at the same time". JS is single threaded so there is only ever one thing at a time happening

thheller05:09:20

ns is a special form so running it in a do is not really supported, although I'm no entirely sure how self-host handles that case

borkdude06:09:06

@thheller I understand but async eval can be interleaved and this might mess things up when you have multiple evals running. Same reason why async mutex exists in JS, that’s where my question is coming from.

borkdude06:09:15

I have a similar problem in SCI where I bind ns to a certain value and then pop the value in a finally promise function. But this could go wrong if your UI triggers multiple evals. So I wonder if self hosted has similar issues and if not, how that is handled.

thheller06:09:38

again .. there is no such thing as multiple evals running. they are all in a queue in the event loop

thheller06:09:49

if you want more control over the queue make your own

thheller06:09:15

but if you use setTimeout like your example above they will execute in that order, it won't randomly switch

thheller06:09:56

but to be entirely sure you should use your own queue regardless

borkdude06:09:17

So that’s what I’m asking. Since evals are handled using callbacks in self hosted, and you evaluate multiple expressions in one go, are they interleaved or executed automatically

borkdude06:09:56

Atomically (ff ing phone)

thheller06:09:10

no, the compiler doesn't manage queues for you. not all actions are async so most of the time things just execute directly

thheller06:09:59

it is async because things like ns need to do async IO. most other forms do not, really only ns and require

borkdude07:09:28

Yeah, exactly the same for NBB. But because the ns form is async there, every top level form is executed async, one by one, chained in promises. Yielding one composite promise per eval. So if you trigger multiple evals the you might have that top level expressions are executed interleaved. But perhaps I should just not support that

thheller07:09:59

IMHO there is no such thing as an "async mutex" in JS. what most people describe that as I would call a queue 😛

borkdude07:09:48

Well sure. I have tried one such impl yesterday which imitated a lock. It almost worked. But nested load-strings didn’t. Because the inner one could not acquire the “lock”

thheller07:09:18

don't build on promises that just creates nightmares

thheller07:09:20

you can't ever lock because of the single thread. the best you can do is queue. so I'd recommend staying from anything that pretends to emulate a lock and instead switch your program to be queue based from the start

borkdude07:09:21

I quoted the word lock. I should just paste the example I used, I understand there is a single thread ;)

thheller07:09:20

just saying .. there are many JS libs that pretend you can lock and stuff like that. just so your code looks more like other platforms where you can actually lock. I don't think that is a valid way to develop JS code

borkdude07:09:22

So does self-hosted use a queue or should you use one yourself or doesn’t it have any of these issues?

thheller07:09:41

no, build your own

borkdude07:09:07

But it can have these issues? Yes or no?

thheller07:09:37

yes, everything that needs to go async has these issues when also accepting inputs at the same time

borkdude07:09:02

How would you handle nested evals with a queue?

thheller07:09:58

in shadow-cljs I run each watch worker in a single thread and core.async channels to manage that. works well but isn't necessary, just an atom like the compiler env should be enough

thheller07:09:00

for me its just easier to think in channels with the downside of being a little harder to debug at times since some state is in go loops which are harder to inspect

thheller07:09:04

so if I was going to do it again I'd maybe just use a single atom

thheller07:09:57

so you put a ::work-queue in the compiler env and only ever append to that

thheller07:09:30

then after adding you check if work is currently pending, aka. a timeout pending. if so you do nothing, otherwise you set that timeout

thheller07:09:58

the timeout then picks the first item to work off and on each "eval" result you repeat the cycle

thheller07:09:08

since its all single thread you don't even need to worry about locks and stuff which you'd otherwise have

borkdude07:09:22

I basically just wanted to verify if self-hosted had similar problems and if so, if it doesn't protect users from these problems, I probably won't either, but will just write some docs that users should use a mechanism (queue) to protect themselves. The use case for interleaved eval is probably niche anyway, but you never know.

thheller07:09:07

what are you building now? sounded like something you were building for your own uses? 😛

borkdude07:09:56

@thheller normally nbb is used quite similar to how CLJS works: namespaces at the top. one program execution started from the main entrypoint let's say

borkdude07:09:07

but I'm exposing an API and you never know how people are going to use this API :)

thheller07:09:02

then I'd probably manage that for them? its not that much work for you to switch your code to (swap! env your-eval code callback) instead of (your-eval code callback) 😛

borkdude07:09:00

Doing too much can have disadvantages as well. I'll think more about it :)