core-async

maybenot 2024-10-29T19:30:16.093709Z

I was thinking about how core.async may play with virtual threads, and my assumption is go macro will not be needed, and everything will happen in a “blocking” manner (thread, <!!, etc) but without actual underlying thread blocking. Is it correct or I’m missing something?

2024-10-29T19:30:49.612289Z

yeah that's basically what he said in the conj keynote

2024-10-29T19:31:08.251689Z

you still want it to return on a chan

2024-10-29T19:31:18.802929Z

but he made it sound like a very straightforward change

seancorfield 2024-10-29T19:31:23.281689Z

go will still exist but not do the wild macro expansion; channel ops can then be blocking (on vthreads).

seancorfield 2024-10-29T19:32:13.614619Z

It sounded like Alex et al are trying to figure out a mostly API-compatible version of core.async that uses vthreads.

maybenot 2024-10-29T19:32:36.959589Z

Now I definitely want to see that keynote:)

2024-10-29T19:32:52.141029Z

oh yeah they were going to add another macro as well that just explicitly puts you on a vthread

seancorfield 2024-10-29T19:33:01.813019Z

Not sure I'd categorize it as "a very straightforward change" tho' 😄

2024-10-29T19:33:20.837089Z

iirc he made it sound rather easy

2024-10-29T19:33:23.198499Z

might misremember

seancorfield 2024-10-29T19:33:28.487869Z

Was it in the Design in Practice in Practice talk? I thought it was in the Clojure 1.12 unsession?

2024-10-29T19:33:42.922189Z

yea the design in practice in practice talk, the keynote

2024-10-29T19:33:51.579729Z

the vod will reveal all

2024-10-29T19:43:30.144659Z

Oh, nice to hear they're working on this

2024-10-29T19:44:40.316449Z

Virtual threads are also still very new, jumping to then reduces the code in core.async a ton, as long as you continue to use them as if they were go blocks (no blocking) then it is great, but determining what actually is blocking is a lot trickier

2024-10-29T19:45:21.707029Z

I haven't looked in a bit, but at least earlier this year there were gists showing trivial deadlocking behaviors using virtual threads

2024-10-29T19:46:51.061699Z

really? I thought the only caveat was synchronized blocks

2024-10-29T19:47:04.734739Z

I would assume the deadlocking rules are the exact same as with platform threadas

2024-10-29T19:47:14.999099Z

It is possible it was synchronized related

2024-10-29T19:48:02.592359Z

that's more to do with pinning rather than deadlocking. but yeah, clojure lazy seqs used synchronized pre 1.12, but that's been patched

2024-10-29T19:48:28.009329Z

I think you can end up in situations where all the carrier threads are pinned (holding a monitor) but waiting on something from a virtual thread that cannot run

2024-10-29T19:48:53.375619Z

yeah yeah, that's the ultimate risk: that you lock up the state machine

2024-10-29T19:49:00.319939Z

(same risk in core.async)

2024-10-29T19:49:29.155739Z

the real risk for virtual threads in core.async is for code that clojure itself doesn't own using synchronized blocks

2024-10-29T19:49:47.884189Z

users are gonna have to have a pretty good idea of the libs they use

2024-10-29T19:50:01.216489Z

but apparently they're working on that at the JDK level

2024-10-29T19:50:19.748599Z

When I was at world singles I don't recall ever locking up the code.async threadpool, but we made a few attempts to switch to virtual threads for various things but I think newrelic related instrumentation code kept deadlocking

2024-10-29T19:50:48.283189Z

yeah the core.async threadpool size used to be 42 lol

2024-10-29T19:51:41.997359Z

but if you use them correctly, (offload i/o onto threads), you won't have any issues. much harder to avoid with virtual threads, that's fair.

2024-10-29T19:52:15.903799Z

Like, I get the impulse, virtual threads are cool, and doing it at the vm level solves all kinds of issues, but virtual threads are a huge change, and it is still very early for them

2024-10-29T19:53:26.728949Z

pretty sure if they address synchronized block pinning, there would be very few instances where they shouldn't be preferred. until then, I agree with you.

seancorfield 2024-10-29T19:54:48.725409Z

At Conj, I asked a room full of folks in the 1.12 unsession, who was using vthreads in production... One person put their hand up... and they said they've run into "a lot of problems" with them (as have we at work).

😿 1
2024-10-29T19:55:37.561559Z

I would be on them if they would bump datomic cloud to 1.12 😄

2024-10-29T19:57:06.075489Z

I guess if you have to use vthreads, it would allow functions to be used which is the most annoying caveat right now, since you can't use map/reduce, etc. inside a go block But then your go code won't be compatible with non vthread implementations of core.async. And if you usurp thread, it similarly might not be compatible, as there are caveats to vthreads as well, and the amount you'd spawn, if you then ran it without vthreads it wouldn't be great. Etc.

2024-10-29T20:00:27.902239Z

why cant you use map/reduce inside a go block? or do you just mean the state machine would be able to park anywhere, regardless of call depth?

2024-10-29T20:05:03.110609Z

Ya. I mean like you can't take inside a map operation, because the go macro doesn't transform across function boundaries.

2024-10-29T20:06:07.146559Z

gotcha, yeah

2024-10-29T20:09:02.246079Z

I find it the most annoying thing haha. I'm ok being careful about where I do I/O, and also not parking deep inside a call stack, but since Clojure is functional, a lot of like conditional/loops are using functions where you'd want to take/put inside the HOF passed to them. And having to use doseq instead all the time is awkward.

2024-10-29T20:10:10.163289Z

And things like for expanding to HOF under the hood also don't work. It's what makes the GO macro feel like some restricted DSL.

2024-10-29T20:11:24.296439Z

Putting and taking with channels are as a much a side effect as anything else, so doing them in a lazy seq is not great

2024-10-29T20:17:28.840339Z

yeah I never wanted to put/take in map

2024-10-29T20:58:53.151129Z

mapv, transducers, doall, reduce, etc. It's pretty unconventional to be restricted to loop/recur and doseq.

maybenot 2024-10-29T20:15:08.719479Z

Also I was reading https://clojure.org/guides/core_async_go#_why_is_this and stumbled upon this: > Thus the result of (map async/<! chans) is something like "a seq of pending channel operations" which makes no sense at all. Why it makes no sense?:)

2024-10-30T16:54:25.891729Z

The explanation is this: > In short, the go macro can’t do these operations without some serious work

maybenot 2024-10-30T17:01:23.774309Z

I like it much better!

2024-10-30T17:02:04.986169Z

I think what it's saying is that. The way the go macro works, as I understand, is that it'll take what's left to do, and put it inside a function to execute later. And it doesn't know how to do that here. But if you instrumented the byte code itself, than map would just be a bunch of instructions in itself, and it could take those, wrap them, and put them to be executed later.

2024-10-29T20:20:50.381189Z

You'll have to track down tim and ask him.

2024-10-29T20:22:27.722919Z

Something to keep in mind (not related to whatever to the explanation is intended there) is channel operations are side effects, channels are mutable state, mixing side effects with lazy seqs like those produced by map can have very surprising behaviors

2024-10-29T20:22:53.638739Z

yeah, makes sense to me tbh, he even gave a reasonable answer: List<Promise>

2024-10-29T20:23:18.252749Z

regardless, it's not supported, which is also fine

maybenot 2024-10-29T20:25:14.805249Z

Agreed about the side effects, just the explanation seems wrong (types, etc) I first read this a few years ago and had a "hmm" moment, and now I'm reading it again and wanted to see if anyone has a better understanding

2024-10-29T20:25:41.721159Z

Promises naively don't play well with the atomic guarantees in things like alts

2024-10-29T20:26:07.288209Z

Manifold takes the promise approach

2024-10-29T20:26:27.706369Z

idk, probably fairly straightforward to have a construct that does tho(?)

2024-10-29T20:26:39.424159Z

"makes no sense" just seems like an overstatement

2024-10-29T20:26:53.439909Z

could be wrong. wouldn't be the first time.

2024-10-29T20:26:55.676879Z

Oh, I am funemployed and working on a library

🤘 1
2024-10-29T20:28:08.073089Z

Concurrent ml has these things it calls events, which at first glance look like promises, but are not exactly that

2024-10-29T20:29:48.924589Z

The issue I have with manifolds approach is if you have two streams, do a take from each, so you have two promises, then do an alt on the two promises and a result from one, both streams still get a value consumed from them

maybenot 2024-10-29T20:33:28.503029Z

recently learned about channels for java vthreads https://github.com/softwaremill/jox and I believe they also consume both channels inside alt/select, which is kinda counterintuitive (but I also may be just wrong, cause spent only 15 minutes on this)

2024-10-29T21:20:43.483459Z

I hadn't really looked at jox before, I appreciate that it's readme makes a distinction between a rendezvous channel and a buffered channel (even if they are sort of the same thing just configured differently)

2024-10-29T21:27:22.272559Z

core.async is missing https://github.com/softwaremill/jox/blob/main/channels/src/main/java/com/softwaremill/jox/Select.java#L325 (clean up after alts) which can cause problems with resource usages (memory leak like behavior with timeouts) so nice to see it