Fork me on GitHub
#core-async
<
2020-06-03
>
colinkahn04:06:51

Is there a way to know when a channel has closed? Like if I want to do some cleanup in the process using it?

seancorfield04:06:57

@colinkahn It will return nil when you take from it, I believe.

colinkahn04:06:36

@seancorfield right, I guess I'm talking about a channel I'm putting values into.

colinkahn04:06:16

I saw that put! will return false if it's closed, but that isn't immediate feedback, requires something attempts a put first

seancorfield04:06:02

Well, it's async so you pretty much have to interact with it in order to tell whether it has closed or not.

seancorfield04:06:34

What people often do is have an extra "control" channel and then you "alt" across the actual channel and the control channel...

raspasov04:06:18

@colinkahn

(let [ch (chan)
      _ (clojure.core.async/close! ch)]
  (clojure.core.async.impl.protocols/closed? ch))

raspasov04:06:35

You’re probably not supposed to use it 🙂 but it’s there…

raspasov04:06:25

Whenever I’ve needed to check that, there was almost always a way around it… it’s not very idiomatic usage of CSP channels as far as I understand

colinkahn04:06:42

My situation is like this:

(defn read-from-client []
  (let [out-chan (a/chan)
        client (make-client)]
   (.on client "change" #(a/put! out-chan %))
   out-chan))
but I was wondering if there's a way if out-chan gets closed if there's a way to setup something in my read-from-client function that can destroy the client etc.

colinkahn04:06:17

@raspasov yeah it's not checking necessarily that it's closed, but having something happen when it closes

raspasov04:06:05

Make a go loop that takes from the channel… when it returns nil -> means closed

raspasov04:06:45

It can only return nil if it closes

colinkahn04:06:50

but then all values will get read in that loop, so I couldn't have consumers. I guess I could have it be a mult?

raspasov04:06:12

Hm, so you have multiple consumers from that channel?

colinkahn04:06:13

it would be multiple if the original function setup a loop to read until closed

raspasov04:06:35

Multiple potential consumers that need to receive the same value? In that case you can use (promise-chan)

raspasov04:06:55

Maybe I’m not understanding the entirety of the problem

hiredman04:06:44

Use the put! arity that takes a callback, it will get called with true when a value is written to a channel, and false if not because the channel is closed

☝️ 4
colinkahn04:06:49

@hiredman my issue with that was that if my client "change" callback isn't called again the client doesn't get cleaned up because another put is never attempted

raspasov04:06:29

@colinkahn how can that channel get closed? do you call (close! …) on it potentially in your code?

colinkahn04:06:51

that was my plan, whoever calls the read-from-client function could later close the out-chan, and I was hoping I could have a side effect of closing it be destroying the client instance

raspasov04:06:55

I would say… since you control the (close! … ), just cleanup right after you call close

raspasov04:06:20

Wouldn’t that work?

hiredman04:06:20

Destroying the client on close would be a race condition

hiredman04:06:43

After you close a channel it can still have stuff in it

raspasov04:06:56

@hiredman mmmmm…. rrrright…

colinkahn04:06:09

ah didn't think of that

raspasov04:06:27

yea… that’s why closing channels is just not a very good idea in general… are you sure you need to do this cleanup stuff? Maybe you can arrange it so that this “change” handler just persists for the duration of the program

raspasov04:06:55

Maybe there’s a way to have a long-lived/global out-chan that is just one channel and constantly receives “stuff”

colinkahn04:06:24

not really, it needs to be created/destroyed when certain state changes, so new information can be used to create it

raspasov04:06:28

That way you don’t have to do constantly have to close! and setup a handler

raspasov04:06:59

Maybe there’s a way to have a channel with a transducer, that does some sort of logic?

raspasov04:06:11

When state changes…

colinkahn04:06:38

yeah I think it's possible, since it's still a singleton more or less

raspasov04:06:48

Or even have a global out-chan, and then have one (go (loop … )) that does the processing and decides there and then what to do with that data

raspasov04:06:31

Setting up/destroying a new handler on every message or frequently is just a recipe for race conditions and further issues like @hiredman very well pointed out

raspasov04:06:11

You’re almost getting nothing out of core.async then vs regular callbacks… you might be even making your life harder

raspasov04:06:59

At least that’s my feeling from my partial information perspective about the problem 🙂

colinkahn04:06:33

well I think the answer to the original question is pretty clearly a no, which makes sense for those reasons

raspasov04:06:23

Yea… the one reliable way, again, is to have a loop, and when that returns a nil -> means closed, and exhausted of all messages

raspasov04:06:50

Since the loop would have taken all messages before receiving that final nil

raspasov04:06:57

But actually thinking about what @hiredman said about the race condition… cleaning up after a (close! …) might not be a problem, since we’re only talking about removing that callback… that wouldn’t interfere with any leftover data on the channel as long as there’s active takers that will process that data (as far as I understand)… but again that can create a problem where in between calling (close! ..) and actually removing that callback you might try to put! onto a closed channel!

raspasov04:06:20

So it’s not really a problem about the channel… but the fact that you might accidentally drop data: 1. (close! ch) 2. DATA SHOWS UP, we try to (put! ch-that-just-closed) 3. remove handler

raspasov04:06:39

So it still is a problem 🙂 just a different kind of problem…

raspasov04:06:30

So all of that make long-lived channels better IMHO for that kind of a situation

colinkahn04:06:33

at that point I wouldn't expect step 2 to succeed anyways

colinkahn04:06:53

but that just might be this particular case

raspasov04:06:54

Yea but what do you do with that data? Drop it? if that’s ok, fine

colinkahn05:06:08

right, dropping it in this case would be fine

raspasov05:06:45

In that particular case you might be OK with that then… unless @hiredman finds another problem with my logic 🙂