Core.async nil on unclosed channels

208 views
Skip to first unread message

Alejandro Ciniglio

unread,
Apr 7, 2014, 11:26:55 AM4/7/14
to clo...@googlegroups.com
Using core.async, I've understood the convention to be that if you take nil from a channel, that channel is closed. This seems to hold for most cases, but I've found a corner case when using map< that lets you pull nil from a channel that is not closed. 

(def a (chan))
(def c (map< seq a))
(go (prn (<! c)))
(>!! a [])
; => nil nil ;; [one nil is printed, one is returned]
(go (prn (<! c)))
(>!! a [1])
; => nil (1)

This can be chained as well (e.g. (map< identity (map<  seq a)) ), and nils just flow through. 

From looking at the implementation, it's apparent that this happens because the function application of map happens when taking from the output channel so nil is not technically on the channel, (unless it flows through to another map).

Is this a bug or is my mental model of nil => closed incorrect?

Thanks,
Alejandro

James Reeves

unread,
Apr 7, 2014, 11:36:21 AM4/7/14
to clo...@googlegroups.com
This looks like a bug to me. A lot of the internal core.async functions rely on nil values indicating the channel is closed.

- James


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Timothy Baldridge

unread,
Apr 7, 2014, 11:41:50 AM4/7/14
to clo...@googlegroups.com
This is a interesting side-effect of the way that map< is implemented. Internally map< is not actually using channels, but is using the channel protocols. It's creating something that looks like a ReadPort, but before handing values to callbacks it applies a function to the value. So map< doesn't actually create a channel at all. 

I actually didn't think about this until today (Rich wrote the original code), it's a rather neat way to go about.

Timothy


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Timothy Baldridge

unread,
Apr 7, 2014, 11:43:24 AM4/7/14
to clo...@googlegroups.com
But yes, we should probably at least put a note in the docs for map< stating "returning nil from the mapping function can result in undefined behavior". Or add an assert somewhere perhaps. 

Timothy

Gary Trakhman

unread,
Apr 7, 2014, 11:52:33 AM4/7/14
to clo...@googlegroups.com
I'm currently running into a bug where it seems like channels aren't being closed down like they should.  This manifests in everything blocking up after many websocket connections.  map< and pipe are involved, I think this is the clue I needed :-).

#(async/map< pr-str (async/tap a-mult (async/chan)))

The return value from this function was expected to be a channel that the jetty7 core.async websocket integration creates and manages during the lifecycle of a websocket connection.

if map< isn't returning a channel, then I guess I can't expect the jetty-7-websockets integration to do the right thing when it tries to close it!

Gary Trakhman

unread,
Apr 7, 2014, 11:54:26 AM4/7/14
to clo...@googlegroups.com
Ah, scratch that.  I see from the source it indeed closes the source channel.  Was hoping that was the clue I needed.

Alejandro Ciniglio

unread,
Apr 7, 2014, 1:32:51 PM4/7/14
to clo...@googlegroups.com, ja...@booleanknot.com
Yeah, that seems to be the best practice that's promoted as well. Another gotcha with this implementation is that since it's done via extending the channel protocol (specifically take!), it doesn't actually apply the functions effects unless someone is reading from the channel. This could be an issue if you want side-effects only from the map< call.

Timothy Baldridge

unread,
Apr 7, 2014, 1:39:09 PM4/7/14
to clo...@googlegroups.com
That's the case with clojure.core.map as well, don't consume the lazy seq the side effects aren't run...in short, map is not for side effects. 

Alejandro Ciniglio

unread,
Apr 7, 2014, 1:46:25 PM4/7/14
to clo...@googlegroups.com, clo...@googlegroups.com
Sure, except you can use doall to realize the sequence from map, but there's no equivalent for core.async.map<. I guess you could wrap it in something that constantly tries to read from the output channel?
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/Hnjg9ovh0uA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Timothy Baldridge

unread,
Apr 7, 2014, 1:50:18 PM4/7/14
to clo...@googlegroups.com
(async/into []) is probably the closest thing to doall

Alejandro Ciniglio

unread,
Apr 8, 2014, 12:28:55 PM4/8/14
to clo...@googlegroups.com
So should I open a bug on this map< behavior? This also breaks composability with things like filter< because they assume that nil being returned implies a closed channel, and proceed to close their output channel...

e.g. 

(filter< identity (map< seq c))

will close the output channel after something like (>!! c [])

Reply all
Reply to author
Forward
0 new messages