[Haskell-cafe] Correct way to "catch all exceptions"

Michael Snoyman michael at snoyman.com
Thu Jul 11 15:56:23 CEST 2013


On Thu, Jul 11, 2013 at 3:44 AM, John Lato <jwlato at gmail.com> wrote:

> Hi Michael,
>
> I don't think those are particularly niche cases, but I still think this
> is a bad approach to solving the problem.  My reply to Erik explicitly
> covers the worker thread case, and for running arbitrary user code (as in
> your top line) it's even simpler: just fork a new thread for the user code.
>  You can use the async package or similar to wrap this, so it doesn't even
> add any LOCs.
>
> What I think is particularly niche is not being able to afford the cost of
> another fork, but I strongly doubt that's the case for Warp.
>
> The reason I think this is a bad design is twofold: first maintaining a
> list of exclusions like this (whether it's consolidated in a function or
> part of the exception instance) seems rather error-prone and increases the
> maintenance burden for very little benefit IMHO.
>
> Besides, it's still not correct.  What if you're running arbitrary user
> code that forks its own threads?  Then that code's main thread could get a
> BlockedIndefinitelyOnMVar exception that really shouldn't escape the user
> code, but with this approach it'll kill your worker thread anyway.  Or even
> malicious/brain-damaged code that does myThreadId >>= killThread?
>
> I like Ertugrul's suggestion though.  It wouldn't fix this issue, but it
> would add a lot more flexibility to exceptions.
>
>
>
I've spent some time thinking about this, and I'm beginning to think the
separate thread approach is in fact the right way to solve this. I think
there's really an important distinction to be made that we've all gotten
close to, but not specifically identified: the exception type itself isn't
really what we're interested, it's how that exception was thrown which is
interesting. I've put together an interesting demonstration[1].

The test I've created is that a worker thread is spawned. In the worker
thread, we run an action and wrap it in a "tryAll" function. Meanwhile, in
the main thread, we try to read a file and, when it fails, throw that
IOException to the worker thread. In this case, we want the worker thread
to halt execution immediately. With the naive try implementation (tryAll1)
this will clearly not happen, since the async exception will be caught as
if the subaction itself threw the exception. The more intelligent tryAll3
does the same thing, since it is viewing the thrown exception as
synchronous based on its type, when in reality it was thrown as an async
exception.[2] The only approach that handles the situation correctly is
John's separate thread approach (tryAll3). The reason is that it is
properly differentiating based on how the exception was thrown.

I'm going to play around with this a bit more; in particular, I want to see
how this works with monad transformer stacks. But I at least feel like I
have a slightly better conceptual grasp on what's going on here. Thanks for
pointing this out John.

Michael

[1] https://gist.github.com/snoyberg/5975592
[2] You could also do the reverse: thrown an async exception synchronously,
and similarly get misleading results.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20130711/903d3a5c/attachment.htm>


More information about the Haskell-Cafe mailing list