My team is trying to wrap our heads around some recent changes. It seems the bounded threadpool was removed recently: https://github.com/clojure/core.async/commits/master/src/main/clojure/clojure/core/async/impl/dispatch.clj Why did we do that? Is there something to replace it? I see some mention of a :compute workload tag, but it doesn't seem it does anything except separate a few cached-threadpool-executors from each other.
For my own curiosity, what was the reason to change the Go pool? So I stop my conjecture.
Preparing for virtual threads support.
At least I believe so. See: https://clojurians.slack.com/archives/C05423W6H/p1744693925811859?thread_ts=1744693925.811859&cid=C05423W6H
I think because go will use virtual threads in the future, which are unbounded. The idea is that if you move back to a JVM without virtual thread now it uses an unbounded cached pool so the behavior will remain the same.
They probably also didn't want to carry that extra thread pool (the old go one) given when it uses vthreads it would be doing nothing.
I'd like some way to limit the number of threads used in a production setting. Our whole app doesn't use core.async, just one thing (cognitect.aws). Vthreads wouldn't use up the resources, I guess, but it wouldn't solve that either. (Maybe there's another way around that specific issue) This seems like a semantic change from fast(er) failure when you block the fixed thread-pool to exhausting your JVM threads.
I think there's better support for providing your own pools now. You can set clojure.core.async.executor-factory system property and provide your own factory for the executor pools.
ah I see
yeah, I think that's what I was looking for
What I gathered is: Workload: • :io - this is for io-thread • :mixed - this is for thread • :compute - this is for flow • :core-async-dispatch - this is for go
Your factory takes a workload and returns the pool to use for each of the above, can be the same for all or different, up to you.
aws-api calls async/thread: https://github.com/cognitect-labs/aws-api/blob/f9490bad5c8e58214bdb40926c27898802a538cd/src/cognitect/aws/util.clj#L291 which would eventually be considered a mixed workload, and have no limiter. In an older version of core.async, I'm pretty sure that's unbounded too. For go-blocks in the new impl, go-impl calls dispatch/run, calls (exec r :core-async-dispatch), that keyword is the workload we'd have to hook
I wonder what happens to resource consumption with all the apps already out there changing to this default executor.
It's unlikely that someone would write new code against a newer version that appears to work, but have it eventually run on an older version of core.async and block the fixed threadpool.
I was thinking we lost a feedback mechanism, but just found a new one I didn't know about: https://github.com/clojure/core.async/blob/e0ba619dc6bae7d2d0e33e58b9301a2233daca8d/src/main/clojure/clojure/core/async.clj#L12-L17
Does the ns docstring clarify things for you?
Try not to examine impl when a doc is available
what got me here won't get me there?
I don't think it's clear at all. Go-blocks were supposed to be non-blocking i/o, and now it appears it's ok to use them in what was considered a wrong way in the past.
no, that is not correct
We have checks for blocking ops, but you can call any java api
go blocks are subject to the same restrictions as before
sure, an individual go-block acts the same way, but it might have never run before b/c the executor was hosed.
I do not know what your referring to wrt hosed
> This seems like a semantic change from fast(er) failure when you block the fixed thread-pool to exhausting your JVM threads.
don’t know what that means, sorry
If you want to have unrestrained blocking, IO or Chan or otherwise, use io-thread
I want the opposite, I want constrained behavior in production
now I see I can set an executor for that, but I think it's easy to miss
what happens if someone does blocking i/o in go-blocks, is it fine with virtual threads?
what happens today?
go blocks must continue to work in the same way whether vthreads are available or not
thus you can't do blocking IO in them.
If you spin up go-blocks in a quick loop, the executor will make new threads and use more memory (unrealistic but possible). When it was a fixed-thread-pool, some would have had to wait.
https://github.com/clojure/core.async/blob/e0ba619dc6bae7d2d0e33e58b9301a2233daca8d/src/main/clojure/clojure/core/async.clj#L175-L177 this docstring is wrong now, I think. Blocking stuff will work with an unbounded thread pool today and would have broken in the past.
Is that fine? maybe it's fine. I'm not clear on it, though.
check out this thread linked earlier: https://clojurians.slack.com/archives/C05423W6H/p1744923920205779?thread_ts=1744693925.811859&cid=C05423W6H
I think this doesn't change semantics for existing code that already worked on older versions of core.async, except for possibly increased resource usage and throughput. New code on new jvms should probably just not use go.
This disconnect seems to be that runtime behavior !== semantics, and I'm not sure that standard is consistently applied
Maybe empirically this is a fine change, but it did surprise someone on my team. We worry about being woken up at night due to things like unpredictable resource usage and want to lock that down as much as possible.
I also expected go-blocks to be run on a fixed thread-pool just like they did 10 years ago, so I dug into it.
Hmm, if there were no change to semantics, then it would have also been fine to keep it a fixed-thread-pool for older jvms by default.
we did have this discussion at length in the team, specifically around changes in resource behavior. it's a subtle issue, but generally we do not consider changes in resource behavior to be breaking in this sense. applications always need to manage and tune for resource usage, which changes as we get new jvms, new clojure, new libs, new containers, new hardware, etc. we released 3 alphas asking people to test and provide feedback on this kind of thing, still happy to hear more. keep in mind that go blocks should not be doing either blocking I/O or long compute. spinning up large numbers of go blocks in a quick loop is not a typical use case
Makes sense, thanks. I think the old fixed threadpool was not an appropriate first line of defense for something like limiting the concurrency in the aws api client, and I suggested we just front it with something else. I'm not sure why I didn't see the release notes, or actually where to find them except looking at the repo.
The flip side here too, is an unbounded thread pool behind go solves some of the deadlock issues that you could experience where things would get mutually blocked on one another.
It also means it takes up less memory when usage of core.async is small, as threads are only created as they are needed.
You should never be deadlocked if you follow the constraints of go, which have not changed, so that is not the reason here