core-async

licht1stein 2024-02-26T17:12:31.903059Z

Hi I’m definitely under-qualified to even be thinking about this, but I have to ask. What are the downsides of replacing core.async/go with something like this:

(defmacro go
  [& body]
  `(let [ch# (a/chan 1)
         captured-bindings# (Var/getThreadBindingFrame)]
     (CompletableFuture/runAsync
      (^:once fn* []
                  (Var/resetThreadBindingFrame captured-bindings#)
                  (try
                    (let [ret# (do ~@body)]
                      (a/put! ch# ret#))
                    (finally (a/close! ch#))))
      (Executors/newVirtualThreadPerTaskExecutor))
     ch#))

seancorfield 2024-02-26T17:39:28.718009Z

Thread-pinning is one issue. Although Clojure 1.12 (Alpha 5) introduced changes to make pinning less likely in core code, there are still plenty of things "in the wild" that use Java's synchronized and could pin vthreads to O/S threads. The MySQL database driver is currently pretty bad for this. So a lot would depend on what exactly your go-using code is doing -- esp. if you are using Clojure 1.11 or earlier.

licht1stein 2024-02-26T20:34:25.338849Z

@seancorfield thanks for the explanation! Is there any practical reason to want to use these virtual threads?

seancorfield 2024-02-26T20:36:51.928499Z

There are plenty of practical reasons -- but the question is: what sort of work are you doing? vthreads work best for tasks with I/O (since that's where the park/resume hooks have been added in the JDK/JVM) rather than heavily CPU bound... but you wouldn't use regular go blocks for I/O-based work.

seancorfield 2024-02-26T20:37:22.358899Z

So there's a sort of conflict between how you are supposed to use go blocks and when you would want vthreads...

licht1stein 2024-02-26T20:39:30.798969Z

I use go-loops to wait for incoming messages for high-traffic websockets and process them once received. So it’s a mix of I/O and calculations.

licht1stein 2024-02-26T20:40:18.952669Z

The idea was to rewrite go and subsequently go-loop using vthreads, to use them. But I’m afraid of bugs 🙂

licht1stein 2024-02-26T20:42:11.750649Z

If not go-loops, what should I use for such work? @seancorfield

seancorfield 2024-02-26T20:58:46.829599Z

If you're using go-loops already, and it works, that seems... fine?

licht1stein 2024-02-26T21:00:10.092409Z

Thing is I’m not sure it works. For example I’m now debugging a problem, where if I’m creating 3 parallel workloads everything works, but when I add fourth everything breaks. Although they are produced by the same function, so the only difference I can imagine is the number of futures used under the hood.

seancorfield 2024-02-26T21:02:33.085369Z

If you're waiting on I/O in go blocks, that sounds like it runs contrary to recommended practices -- and you should be using threads instead. So in your case, explicitly using vthreads would probably be a good thing -- but not by subverting how go works 🙂

licht1stein 2024-02-26T21:03:04.646509Z

Maybe something like promesa? It seems to have similar API to core.async, but uses vthreads under the hood

seancorfield 2024-02-26T21:03:37.250619Z

No idea. I've never used promesa.

licht1stein 2024-02-26T21:04:01.628869Z

Me neither. I’ll try and report 🙂

licht1stein 2024-02-27T12:43:22.798359Z

Ok, so promesa can be used as a drop-in replacement for clojure.core.async. It has a mostly similar api, so the changes required are minimal, and under the hood it uses virtual threads. It also has a better mult model for my case, mult can be created without a source channel and used as a broadcasting destination (a write-only channel, that normal channels can tap into).

licht1stein 2024-02-27T12:44:01.980409Z

As additional benefit is that it doesn’t color you functions and allows any kind of code within go-blocks, so it makes things a little easier too. @seancorfield

licht1stein 2024-02-27T12:45:00.674949Z

https://funcool.github.io/promesa/latest/channels.html

seancorfield 2024-02-27T16:51:11.384639Z

Good to know. I need to put that on my list to investigate.