update: There is a bug in the code, it should use to-chan
not onto-chan
=> the input is never read
Could somebody be so kind and tell me why this future is forever :pending:
(future
(->> ["hello"]
(a/onto-chan (a/chan)) ; <- error, should be (a/to-chan)
(a/transduce
(map #(throw (ex-info "Fake exc" {:v %})))
(fn [a v] v)
{})
(a/into [])
(a/<!!)))
Is it because the map
that throws does not consume the item from the input channel and thus the channel will never close? If that is the case then I guess I need to wrap all my transducers into an explicit try-catch
(or set an error-handler on a channel, though it is unclear to me where to do it in this code snippet)~
Note: This works as expected if I (map identity)
instead of throwing.
So it seems that if the transducer throws, the item on the channel is not consumed => the input channel is not closed => <!! never finishes.a/transduce
and a/into
require that the source channel be closed before they will produce any results
From onto-chan docs:
> By default the channel will be closed after the items are copied
And this works if I use (map identity)
instead of throwing
hm, though maybe a/onto-chan
should be closing that chennl
ah
probably, the thrown exception doesn't end up putting anything onto the channel
so it's blocked at <!!
forever
<!! doesn't wait until there is something on the channel. It will also finish if the channels is closed.
right, but the channel is coming from the into, right?
yes, but into will return an empty collection of it gets a closed channel I believe
right, but into is getting the channel from transduce, and it's not getting to the close part of the code because an unhandled exception is being thrown
presumably
This (a/<!! (a/onto-chan (a/chan) []))
returns (nil)
We can presume a lot 🙂 I'd like an explanation from somebody who actually knows.
k
but thank you for trying, I appreciate it!
Has somebody written about best practices regarding error handling in core.async, especially in the higher level constructs such as pipeline and transduce? My search hasn't discovered much useful...
I don't know if this is considered best practise, but i'm a fan of writing async functions that return a vector [success-channel error-channel]
and handling each case either directly, or with helper functions to handle the error case
Yes, but that's because you didn't close your channel. So it isn't entirely related, but I understand your concerns
Here's an example, where i'm closing the channels to make sure there are no deadlocks
https://github.com/district0x/ethlance/blob/newlance/src/ethlance/server/filesystem.cljs#L10
Note that channels return nil
when they are closed, so I can use the same pattern if I were consuming from a success channel. I would just check to see if what is returning is nil and then consume the error channel to see if there were any errors to either handle and display an error message, or throw if i'm producing an error that was placed on the channel
Ok but eg with transduce, do I need to wrap the body of my xform and reducing fn in try-catch?
I suppose that would be up to you 😛
Also depends on where you're using the functions, your philosophies on how errors might be handled. Some people like their software to fail fast and hard so they can diagnose the issue faster. Some people like to catch everything and print to an error log.
@seancorfield usually has insightful things to say about these sorts of things
Well, it is not fully up to me. As demonstrated by https://clojurians.slack.com/archives/C05423W6H/p1571669436015600, missing error handling will lead to deadlocks.