Fork me on GitHub
#core-async
<
2021-11-03
>
didibus06:11:20

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

phronmophobic06:11:05

> 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.

didibus06:11:58

Hum, I mean if you equate a "puts" with an "offer"

phronmophobic06:11:27

> Puts a val into port if it's possible to do so immediately. [...] Returns true if offer succeeds.

phronmophobic06:11:02

> Once full, puts complete

didibus06:11:27

Right, so that just means it won't park

didibus06:11:44

So the puts complete, as in, it won't park or block

didibus07:11:14

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

phronmophobic07:11:38

(let [ch (-> (chan (async/dropping-buffer 1)))]
  (async/>!! ch 42)
  (async/offer! ch 42))
;; true

didibus07:11:52

Ya, the fact it behaves the same for dropping-buffer might indicate its by design

phronmophobic07:11:34

it just tries to put, never blocks, and returns true if the offer succeeds. I think that means if the put succeeds

didibus07:11:37

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

didibus07:11:38

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

phronmophobic07:11:08

interestingly:

(let [ch (-> (chan))]
  (async/close! ch)
  (async/offer! ch 42))
;; false

didibus07:11:31

Ya, I would have made offer! return true or false based on if the value is transferred

didibus07:11:14

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

didibus07:11:30

Or at least if it worked like that, it solved my use case 😛

didibus07:11:53

(def foo (promise-chan))
(go (and (offer! foo 1) (println "Go 1 won!")))
(go (and (offer! foo 2) (println "Go 2 won!")))

didibus07:11:21

This is my use case. Not sure how I can solve it.

phronmophobic07:11:04

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!"))))

phronmophobic07:11:33

and the foo process will write true to the first and close! the rest

phronmophobic07:11:05

there's probably something more elegant, but that's what I can think of off the top of my head

phronmophobic07:11:51

although, this wouldn't work with a promise chan since a promise chan can only receive one value

didibus07:11:59

I'm not sure I understand what will deliver to let-me-know-chan ?

didibus07:11:45

Oh I see what you meant now, ya with promise-chan I won't see that like they came in a particular order.

didibus07:11:51

Since the others will be dropped

phronmophobic07:11:19

(go
  (let [[foo-val foo-callback] (<! foo)]
    (>! foo-callback true)
    (close! foo)
    (go (loop []
          (when-let [[_ callback-ch] (<! foo)]
            (close! callback-ch)
            (recur))))
    ...))

didibus07:11:22

I don't think that works

didibus07:11:23

Oh, hum... ok no I see ya it works

phronmophobic07:11:55

there's probably a simpler way to do it

didibus07:11:05

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.

didibus07:11:43

It also kind of defeats the point of using the non-blocking offer! just to block right after to know if your offer! worked 😛

didibus07:11:30

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!"))))

didibus07:11:31

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!

phronmophobic07:11:22

are you just trying to figure out which go loop goes first?

phronmophobic07:11:42

it seems like your example wouldn't actually deliver anything

phronmophobic07:11:27

ah ok, I think that works

didibus07:11:36

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.

phronmophobic07:11:01

in that case you might want to use >! instead of offer

didibus07:11:49

(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))

didibus07:11:08

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.

phronmophobic07:11:44

You could also have a separate, shared cancel channel that is closed on cancellation

didibus07:11:54

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)

phronmophobic07:11:35

and whoever wins just closes the shared cancel chan

didibus07:11:28

Ya, but I find that gets intractable quick

didibus07:11:31

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)))))

didibus07:11:22

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

didibus07:11:25

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))

👍 1
didibus07:11:47

So if I see that I do have race condition somewhere I'll remember this trick, thanks for the help @smith.adriane

hiredman15:11:21

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

didibus22:11:50

Hum.... ya I guess I could have one go block that just alts! on the channels and on result cancel all the others

didibus22:11:24

Now I can't remember why I didn't use alts! before and just fired a bunch of go block to monitor this... Maybe I just didn't think of it