This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-03
Channels
- # announcements (35)
- # aws (20)
- # babashka (4)
- # beginners (88)
- # cider (9)
- # clara (1)
- # clj-kondo (6)
- # cljsrn (3)
- # clojure (107)
- # clojure-dev (7)
- # clojure-europe (99)
- # clojure-nl (3)
- # clojure-spec (9)
- # clojure-uk (2)
- # clojurescript (28)
- # core-async (53)
- # cursive (11)
- # datascript (1)
- # datomic (2)
- # emacs (20)
- # fulcro (3)
- # graalvm (4)
- # holy-lambda (18)
- # jobs (1)
- # kaocha (7)
- # leiningen (2)
- # lsp (25)
- # luminus (1)
- # membrane-term (52)
- # missionary (8)
- # nextjournal (19)
- # off-topic (16)
- # other-languages (3)
- # podcasts-discuss (2)
- # polylith (23)
- # re-frame (4)
- # reclojure (6)
- # remote-jobs (1)
- # rewrite-clj (36)
- # ring (1)
- # sci (10)
- # shadow-cljs (7)
- # spacemacs (5)
- # sql (20)
- # uncomplicate (1)
- # vscode (3)
- # xtdb (27)
Looks like offer!
always returns true when on a promise-chan:
(def foo (a/promise-chan))
(a/offer! foo 1)
;;=> true
(<?? foo)
;;=> 1
(a/offer! foo 2)
;;=> true
(<?? foo)
;;=> 1
I would consider this a bug, since I was wanting to know if the offer! succeeded or not, as in, was my go block the one to successfully deliver the value of the promise-chan, but I'm asking here in case its by design> offer! Puts a val into port if it's possible to do so immediately.
> nil values are not allowed. Never blocks. Returns true if offer succeeds.
from promise-chan
> Once full, puts complete but val is dropped (no transfer).
That behavior seems to match the docs at least.
> Puts a val into port if it's possible to do so immediately. [...] Returns true if offer succeeds.
> Once full, puts complete
But then the question is what is an "offer", is it a synonym for a put? Or does it imply that the value was put into the buffer as well
(let [ch (-> (chan (async/dropping-buffer 1)))]
(async/>!! ch 42)
(async/offer! ch 42))
;; true
it just tries to put, never blocks, and returns true if the offer succeeds. I think that means if the put succeeds
Ya, but that's strange, because offer! says it never blocks, and a put completing only implies it won't block. So like, it seems wrong
It kinds of prevent being able to use offer! to synchronize things though, at least in my case, because I have a race between two things trying to offer! to the promise-chan, and I need to know which one made it first
interestingly:
(let [ch (-> (chan))]
(async/close! ch)
(async/offer! ch 42))
;; false
Ya, I would have made offer! return true or false based on if the value is transferred
Since offer! never blocks anyways, the fact that the puts complete or not doesn't seem to matter. If the puts fails well offer! wouldn't block anyway
(def foo (promise-chan))
(go (and (offer! foo 1) (println "Go 1 won!")))
(go (and (offer! foo 2) (println "Go 2 won!")))
you could do something like:
;; not a promise chan!
(def foo (chan))
(go (let [let-me-know-chan (chan)]
(when (and (offer! foo [1 let-me-know-chan])
(<! let-me-know-chan))
(println "Go 1 won!"))))
(go (let [let-me-know-chan (chan)]
(when (and (offer! foo [2 let-me-know-chan])
(<! let-me-know-chan))
(println "Go 2 won!"))))
and the foo process will write true
to the first and close!
the rest
there's probably something more elegant, but that's what I can think of off the top of my head
although, this wouldn't work with a promise chan since a promise chan can only receive one value
Oh I see what you meant now, ya with promise-chan I won't see that like they came in a particular order.
(go
(let [[foo-val foo-callback] (<! foo)]
(>! foo-callback true)
(close! foo)
(go (loop []
(when-let [[_ callback-ch] (<! foo)]
(close! callback-ch)
(recur))))
...))
there's probably a simpler way to do it
Each put their own channel and so the one that is in the promise-chan is the one that won. Ya, hum, its a bit ugly haha.
It also kind of defeats the point of using the non-blocking offer! just to block right after to know if your offer! worked 😛
I guess I could do:
(def foo (a/promise-chan))
(a/go (let [id (gensym)] (and (a/offer! foo {id 1}) (get (a/poll! foo) id false) (println "Go 1 won!"))))
(a/go (let [id (gensym)] (and (a/offer! foo {id 2}) (get (a/poll! foo) id false) (println "Go 2 won!"))))
That reminds me of another question I had, is there a way to peak into a channel? poll! for promise-chan works since it won't remove it, but on other channels it would remove the item, was curious if there is a peak!
are you just trying to figure out which go loop goes first?
it seems like your example wouldn't actually deliver anything
in some cases
ah ok, I think that works
In my case its that I have a function that is called race
and it returns the first result of a vector of promise-chan. It also cancels all other operations as soon as one succeeds. So I want the one that succeeds to offer to cancel the others, but the offer! and the loop over others and cancel them I guess can happen in parallel, since each one is in a go block.
Now to be honest, I'm still trying to figure out if its a problem that another one also cancels the other including the one that won, since the one that one already offered the result it might not matter. But I was like, oh well I can just do (and (offer! ...) (cancel ...)) and not worry about it, and then I realized that does not work haha.
in that case you might want to use >!
instead of offer
(defn race
[chans]
(let [ret (a/promise-chan)]
(if (seq chans)
(doseq [chan chans]
(a/go
(let [res (<! chan)]
(and (a/offer! ret res)
(run! #(when-not (= chan %) (cancel %)) chans)))))
(a/close! ret))
ret))
Ah ya, now I remember what I was worried about... So the way my cancellation works, is that it actually just puts a ::cancelled value on the other channels, and the go operations over those channels as they go about their tasks, they check if their return channel has a ::cancelled on it or not, and if they see it does, they just stop what they are doing.
You could also have a separate, shared cancel channel that is closed on cancellation
A pseudo example:
(defn do-something-async
[]
(let [ret (a/promise-chan)]
(a/go-loop []
(Thread/sleep 100)
(when-not (= ::cancelled (a/poll! ret) (recur))
ret)
and whoever wins just closes the shared cancel chan
I'm not sure if I need to protect this bit:
(a/go
(let [res (<! chan)]
(and (a/offer! ret res)
(run! #(when-not (= chan %) (cancel %)) chans)))))
I think in my case I don't need to worry, since whichever won the offer! has won the race, and it doesn't really matter which one tries to cancel the others. But it still got me curious about the problem
For the fun of it, I think this would do what I wanted:
(defn race
[chans]
(let [ret (a/promise-chan)
tmp-ret (a/promise-chan)]
(if (seq chans)
(doseq [chan chans]
(a/go
(let [res (<! chan)
id (gensym)]
(and (a/offer! tmp-ret {id res})
(get (a/poll! tmp-ret) id false)
(do (run! #(when-not (= chan %) (cancel %)) chans)
(a/offer! ret res))))))
(a/close! ret))
ret))
So if I see that I do have race condition somewhere I'll remember this trick, thanks for the help @smith.adriane
You are basically trying to implement a lock, in general you will have a better time representing the critical section as a go block that you run a single instance of and pass stuff to via channels vs have multiple instances of the same code running like you have