Fork me on GitHub
#core-async
<
2018-03-29
>
ajs13:03:10

Why does this create an infinite loop?

(defn tfn [] (let [ch (chan)
                   f  (future (put! ch (doall (pmap inc (range 100)))))]
               (go (while true (let [ret   (<! ch)
                                     total (count ret)]
                                 (println "done")
                                 (println "total is" total)
                                 (println ret)
                                 (close! ch))))
               f))

ajs13:03:46

After it reads from the channel, it closes it, my thinking is that a temporary channel is created and closed on each invocation of the function tfn but instead i get an infinite loop of printlns

ajs13:03:50

the idea is that tfn is non-blocking but still prints some stuff when the future is done, then disposes of this temporary channel after reading and processing the single item put on the channel

tbaldridge13:03:11

@ajs there's no exit to this loop, while true will cause the loop to run forever

ajs13:03:03

what happens to the (<! ch) after the channel is closed, is that why the while-true no longer blocks?

ajs13:03:22

i mean, blocks the printlns

tbaldridge13:03:37

yes, it'll return null once the channel closes, so switch your while to a loop, recurn and when-some

ajs13:03:57

i guess if I'm only putting one thing on the channel like in this case, there is no need for the while loop, just the let...

tbaldridge13:03:01

(loop []
   (when-some [val (<! ch)]
      ...printlns...
      (recur)))

tbaldridge13:03:03

and the when-some is kindof critical, when-let would exit the loop if you send a false value down the channel, when-some will stop the loop only on nils

4
ajs13:03:36

getting rid of the while-true, then is the close! all that is needed for proper cleanup in this function?

tbaldridge13:03:03

yes, close! will mark the channel to eventually start returning nil values. Here's the critical part: core.async defaults to not losing data. This means that the nil won't be returned until all enqueued data inthe channel is taken.

tbaldridge13:03:48

that can trip people up at times, close! is more like (>! ch nil) than a instant operation

ajs13:03:36

will that channel get garbage collected and logically no longer exist when I run this fun? suppose I run it many times, is there anything sticking around that is not cleaned up by this simple close! operation?

tbaldridge13:03:49

Right so channels are GC'd once nothing is using them. Go blocks are turned into callbacks that are attached to channels.

ajs13:03:00

remember that I'm only putting a single item on the channel, and reading it, then closing

ajs13:03:10

i don't have to explicitly read that nil or anything from the close! ?

tbaldridge13:03:12

So most of the time, what's keeping a channel from being GC'd is that a thread is running and isholding a reference to the channel

tbaldridge13:03:33

right, you don't even have to close for the channel to be GC'd

ajs13:03:55

i see, when the future is done running, no reference is held for the channel, is that what you mean?

ajs14:03:21

as in, the future that is explicitly in my function

tbaldridge14:03:08

Well it's not even "when it's done running" if the only reference to the channel is a go block that has a pending take. Then nothing else can be holding on to the channel. So the channel will be GC'd and with it the go block that is waiting for a take.

tbaldridge14:03:04

So blocks and channels will never "go away" when you don't expect, but it's quite possible for chains of channels to deadlock and then be GC'd. But you'd never know, because there'd be no references to that chain in the system.

ajs14:03:12

lots of magic there.

ajs14:03:18

incidentally, i was thinking of you today @tbaldridge because i am playing around with fn-fx. would it be risky to adopt it for a big project? i'm at a point where I'm having to slap a gui on something and setting up a websocket server in my app for a web front end is not exciting, and swing/seesaw just look like the 1990s.

tbaldridge14:03:59

It's not as polished as seesaw, but give it a try.

mattly21:03:41

I just added a dep in leiningen for [org.clojure/core.async "0.4.474"] and when I added a require for that into one of my project namespaces, I get an java.lang.ClassNotFoundException: clojure.core.async.Mutex error when attempting to launch my repl. any ideas?

mattly21:03:12

it happens even without the require

Alex Miller (Clojure team)22:03:39

Is there a stack trace? Who’s loading it?

Alex Miller (Clojure team)22:03:09

That class doesn’t exist anymore in 0.4.474 (it did use to exist but wasn’t being used)

mattly23:03:15

1m I’ll get a stack trace

mattly23:03:29

as best as I can tell, this is, env/dev/user.clj (the user ns, home for the repl), loading myapp.config, loading myapp.fixtures, loading pedestal, and it’s happening in pedeestal somewhere

mattly23:03:34

I’m running pedestal 0.5.3

hiredman23:03:27

are you aot compiling?

mattly23:03:26

I’m running lein repl

hiredman23:03:41

I would clear out any possibly stale stuff in target/ and maybe run lein pedantic

hiredman23:03:08

you are aot compiling, you have a :main or :aot key or whatever it is set in project.clj

hiredman23:03:25

lein is attempting to aot compile your project before starting that repl, which is where that error is happening

mattly23:03:28

aha, no it’s not aot compiling

hiredman23:03:46

so you likely have stale class files in target/

hiredman23:03:36

oh, sorry, right I misread the stacktrace, but check lein pedantic or whatever it is called these days to make sure you are getting the version of core.async you think

mattly23:03:42

bah, yeah I think clean did it

mattly23:03:55

unfortunately our test suite requires being ran against a compiled^H^H^H^H^H^H^H^H^H jar

hiredman23:03:09

jars aren't compiled

hiredman23:03:14

they are zip files

hiredman23:03:23

you can run clojure from a jar without aot compiling

hiredman23:03:52

and there are techniques to limit the amount that gets aot compiled

mattly23:03:40

sorry, dealing with an in-office conversation as well

mattly23:03:06

what I meant to say was that it requires being ran against an uberjar from /target from some other docker

mattly23:03:20

which, really, I’ve considered setting up two copies of the repo

mattly23:03:21

and sometimes my script for running the tests doesn’t clean up properly, and it doesn’t tell me, and I always forget about that

mattly23:03:24

it’s working now, thanks