This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-01
Channels
- # announcements (3)
- # babashka (20)
- # beginners (77)
- # calva (27)
- # cider (5)
- # clara (3)
- # clj-kondo (9)
- # cljs-dev (4)
- # cljsrn (5)
- # clojure (26)
- # clojure-europe (32)
- # clojure-italy (5)
- # clojure-nl (3)
- # clojure-uk (5)
- # clojurescript (25)
- # clojureverse-ops (4)
- # core-async (49)
- # cursive (15)
- # data-science (1)
- # datahike (4)
- # datomic (3)
- # docker (1)
- # events (1)
- # helix (5)
- # holy-lambda (3)
- # introduce-yourself (1)
- # jobs (1)
- # kaocha (2)
- # lsp (15)
- # malli (42)
- # off-topic (18)
- # pathom (18)
- # pedestal (12)
- # polylith (7)
- # rdf (1)
- # re-frame (22)
- # reitit (2)
- # releases (1)
- # remote-jobs (1)
- # rewrite-clj (33)
- # shadow-cljs (85)
- # spacemacs (3)
- # vim (12)
- # xtdb (29)
Is there a function in core.async that lets me put n values in n channels? Something like
(defn multi->!! [channel-val-pairs]
(loop [cvs channel-val-pairs]
(when (seq cvs)
(let [[_ c] (async/alts!! cvs)]
(recur (filter #(not= c (first %)) cvs))))))
something like
(defn multi [channel-val-pairs]
(let [c (async/chan (count channel-val-pairs))]
(doseq [[c v] channel-val-pairs]
(async/put! c v (fn [_] (async/put! c true))))
(async/go
(dotimes [i (count channel-val-pairs)]
(async/<! c)))))
would allow for better concurrency (using alts means only one put can succeed at a time), it returns a channel so you need to use <!! to block on it, but you can also use it inside a go block with <!I think one reason multi
might not exist is that there are many different options for backpressure and timeouts when you're putting on multiple channels with multiple values that will likely depend on the use case
core.async, and a lot of csp inspired libraries are actually kind of primitive about building operations like this
where alt is a disjunction of operations on channels, what you want is a conjunction of operations on channels
thanks, my case is actually just 2 channels, so I wonder if the extra "overhead" of multi
is actually warranted or if I should just do alts!
followed by >!
Usually, it boils down to "how do you want to handle backpressure?"
> atomic conjunctions of operations intuitively, that seems like it would be easy to encourage deadlock
there is a great thesis from a few years ago (https://www.ccs.neu.edu/home/turon/thesis.pdf) which gives you transactions over both refs (stm) and channels (csp like)
I've played around with implementing it in clojure https://git.sr.ht/~hiredman/reagents but it is a toy implementation, I haven't touched it in a while, not sure if the tests pass, I was trying to have a core.async compat layer, but I forget if I ended up giving up on that or not
the way core.async deals with locks makes it hard to interface with a lot of newer atomic cas based ideas, things turn into primitive spin locks, etc
a neat thing I got from the paper that I didn't realize before is !> and <! are the same operation
Usually, it boils down to "how do you want to handle backpressure?"
ie. what happens if one or both channels fall behind?
should that block progress for both channels or just one?
should there be timeouts?
The easy version is just to async/put!
for each channel, but that's just kicking the can down the road with respect to thinking about how the system responds to load and errors
If that's the case, then I think your implementation looks pretty good. It might not matter for your specific use case, but your multi->!!
implementation will have unexpected results if someone ever pass multiple operations on the same channel.
I was thinking something like:
(defn multi->!! [channel-val-pairs]
(loop [ops (set channel-val-pairs)]
(when (seq ops)
(let [op (async/alts!! ops)]
(recur (disj ops op))))))
but, set
should really be a bag, but I'm not sure if there's an easy to find implementation for thatit should be a bag
well, in my head, the abstraction of "do these operations and return when all of them complete" is more useful than "do these operations with the restriction that at most one value can be written to each channel and return when all of them are complete"
but you could do a version with the map, but I would probably call it something else
you could even pass a map
or a set
the set version is broken for that then (which I guess is why you suggested a bag)
or someone could write their own async library that includes atomic conjunctions of operations
More of a general question, what are your go-to tools for figuring out if and where your system is experiencing too much backpressure? My way often just comes down to lots of logging and doesn't seem very efficient, so I wonder what other people are doing. Is there a library to visualize things? I also wonder if there has been any work done in the direction of autoscaling the different parts of a system when it happens?
it isn't so much "too much backpressure" the backpressure is always right, it is a question of where are my bottlenecks
the backpressure is a signal from the slower parts of the system to slow down the faster parts so the slower parts are not overwhelmed. so the thing is identifying the slower parts
I guess the questions I would have are: • where is the backpressure coming from? I/O, other systems, CPU? • is this a distributed system?
For general performance stuff, http://clojure-goes-fast.com/blog/ has some good info
searches for bottlenecks are usually iterative, at any given moment there is one bottleneck, and if you make that not the bottleneck something else is now the bottleneck
@finn.volkel you may find this talk interesting - https://www.youtube.com/watch?v=r-TLSBdHe1A - and if that peaks your interest, you may want to look into the coz
and jcoz
causal profilers. I think in the future as a community we could try to get something more Clojure-specific (similar to the clojure-goes-fast tooling) that can do this kind of profiler analysis on Clojure code with more friendly REPL tooling (e.g. based on forms and/or functions, and not lines of code). Well, at least, one can dream :)
there is a jvm variant of the impl in that talk, so should theoretically work on Clojure
at least one of the problems he describes was actually found on the jvm with an initial env that was sensitive to a java system property
but in practice, I have not found any of that to be necessary to understand or diagnose problem with the tools we have
there is no substitute for having a model (mental at least) of your system under load with tools to validate your expectations
the stupidest technique I know is also one of the most effective - load your system, then take 10 thread dumps. whatever threads show up as RUNNING the most is the bottleneck