core-async

2020-06-03T04:15:51.396400Z

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

seancorfield 2020-06-03T04:19:57.396900Z

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

2020-06-03T04:20:36.397400Z

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

2020-06-03T04:21:16.398100Z

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

seancorfield 2020-06-03T04:22:02.398700Z

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

seancorfield 2020-06-03T04:22:34.399400Z

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

raspasov 2020-06-03T04:26:18.401700Z

@colinkahn

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

raspasov 2020-06-03T04:26:35.402500Z

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

raspasov 2020-06-03T04:27:25.404100Z

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

2020-06-03T04:27:42.404500Z

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.

2020-06-03T04:29:17.405200Z

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

raspasov 2020-06-03T04:30:05.405700Z

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

raspasov 2020-06-03T04:30:45.406400Z

It can only return nil if it closes

2020-06-03T04:30:50.406700Z

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?

raspasov 2020-06-03T04:31:12.407100Z

Hm, so you have multiple consumers from that channel?

2020-06-03T04:32:13.408600Z

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

raspasov 2020-06-03T04:32:35.408700Z

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

raspasov 2020-06-03T04:32:55.409100Z

Maybe I’m not understanding the entirety of the problem

2020-06-03T04:33:44.410600Z

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

☝️ 1
raspasov 2020-06-03T04:34:18.411100Z

@hiredman good idea

2020-06-03T04:34:49.411800Z

@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

raspasov 2020-06-03T04:35:29.412900Z

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

2020-06-03T04:36:51.415700Z

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

raspasov 2020-06-03T04:37:55.417600Z

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

raspasov 2020-06-03T04:38:20.418700Z

Wouldn’t that work?

2020-06-03T04:38:20.418800Z

Destroying the client on close would be a race condition

2020-06-03T04:38:43.419600Z

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

raspasov 2020-06-03T04:38:56.419900Z

@hiredman mmmmm…. rrrright…

2020-06-03T04:39:09.420100Z

ah didn't think of that

raspasov 2020-06-03T04:40:27.421300Z

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

raspasov 2020-06-03T04:40:55.422Z

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

2020-06-03T04:41:24.423Z

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

raspasov 2020-06-03T04:41:28.423200Z

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

raspasov 2020-06-03T04:41:59.423700Z

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

raspasov 2020-06-03T04:42:11.423900Z

When state changes…

2020-06-03T04:44:38.426Z

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

raspasov 2020-06-03T04:44:48.426200Z

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

raspasov 2020-06-03T04:45:31.427Z

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

raspasov 2020-06-03T04:46:11.427700Z

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

raspasov 2020-06-03T04:46:59.428500Z

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

2020-06-03T04:49:33.429600Z

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

raspasov 2020-06-03T04:50:23.430200Z

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

raspasov 2020-06-03T04:50:50.430800Z

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

raspasov 2020-06-03T04:56:57.433200Z

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!

raspasov 2020-06-03T04:58:20.434400Z

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

raspasov 2020-06-03T04:58:39.434800Z

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

raspasov 2020-06-03T04:59:30.435700Z

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

2020-06-03T04:59:33.435800Z

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

2020-06-03T04:59:53.436300Z

but that just might be this particular case

raspasov 2020-06-03T04:59:54.436400Z

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

2020-06-03T05:00:08.436700Z

right, dropping it in this case would be fine

raspasov 2020-06-03T05:01:45.437400Z

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