[Haskell-cafe] ANN: asynchronous-exceptions

João Cristóvão jmacristovao at gmail.com
Thu Feb 6 00:16:06 UTC 2014


Hi again,

> since the module doesn't make any attempt to differentiate between sync and async exceptions.

Well... If I am understanding this correctly, and I might not be:
Michael code efectively distinguishes exceptions thrown asynchronously
with throwTo from synchronous exceptions generated inside the
catchAny. Since the code run by catchAny is run in a separate
'anonymous' thread created by withAsync, there's no way you can throw
it an asynchronous exception (ThreadKilled) with throwTo - you don't
know its thread id.

Any code using throwTo will just know the calling thread, and thus
you've got a way to distinguish between received asynchronous
exceptions (the timeout example) and synchronous exceptions generated
inside the function your passing to catchAny.

Thus, in one hand I do not think "CatchAny" makes justice to the fact
that async exceptions received by the thread using catchAny are _not_
caught by catchAny.

In the other hand, I was just thinking: what if the computation runing
in catchAny in the anonymous thread generates a Ctrl-C, if, for
example, it interacts with stdin.
However, Control.Exception states that:
"UserInterrupt: This exception is raised by default in the __main__
thread of the program when the user requests to terminate the program
via the usual mechanism(s) (e.g. Control-C in the console). "
(emphasis mine)

Thus, the problem does not pose it self. The remaining two
asynchronous exceptions (StackOverflow and HeapOverflow) are again a
result of the inner function execution, and could, from a certain
point of view, be considered synchronous.

So, for all intents and purposes, this catchAny and such _do_ allow to
distinguish between asynchronous exceptions (sent with throwTo) or
synchronous exceptions (generated as a result of the execution of the
computation passed to catchAny). Asynchronous exceptions do not occur
inside the CatchAny associated computation, except perhaps the
ThreadKilled signal forward by its calling thread/parent thread when
it self is killed - an in that particular case, it doesn't really
matter. That asynchronous exception would be caught by catchAny, but
the result returned by the exception handler would not be used - the
calling thread is also dying.

Am I getting this right?
João

2014-02-05 Roman Cheplyaka <roma at ro-che.info>:
> Well, since you happened to ask me (although I am as good a bikeshedder
> as the next person), I think that you don't need to mention Async
> anywhere in the module name, since the module doesn't make any attempt
> to differentiate between sync and async exceptions.
>
> And the fact that the module uses the 'async' library is just an
> implementation detail.
>
> I'd go with something like Control.Exception.CatchAny.
>
> Roman
>
> * João Cristóvão <jmacristovao at gmail.com> [2014-02-05 22:13:19+0000]
>> Roman,
>>
>> By mere chance today I was, about the same time you published your
>> library, working on the suggestion made by Michael in the end of his
>> original blog post: splitting the async exceptions part from
>> classy-prelude (he is ok with this).
>>
>> https://github.com/jcristovao/async-exception
>>
>> I was not yet sure about the namespace, I had opted for:
>> Control.Concurrent.Async.Exception
>>
>> But yours makes more sense,
>> Control.Async.Exception
>>
>> I agree that the two solutions address different problems, and as you
>> say, for controlled situations where performance is critical yours
>> indeed adds less overhead. But for more general solutions, Michael's
>> solution - split from Classy prelude, seems to be the way to go, and
>> thus my 'split' makes sense if you don't need the remaining classy
>> prelude.
>>
>> As such, I was considering the namespace:
>> Control.Async.Exception.All
>>
>> To differentiate from yours, signaling that it handles _all_ exceptions.
>> What do you think?
>>
>> Anyhow, I also think Joachim suggestion (of at least implementing the
>> new exception classes in base-compat) makes sense, so I volunteer to
>> add to the work I already done here:
>> https://github.com/sol/base-compat/pull/2
>>
>> (If the patch gets accepted, of course).
>>
>> Cheers,
>> João
>>
>> 2014-02-05 Roman Cheplyaka <roma at ro-che.info>:
>> > Ok, this clears things up. I misinterpreted your approach thinking that
>> > you're also solving the problem of distinguishing async vs sync
>> > exceptions, only based on how they were thrown instead of their type.
>> >
>> > I now see that it isn't the case -- you're catching *all* exceptions.
>> > (And run the timeout handler in a different thread.)
>> >
>> > So no wonder that asynchronous-exceptions (whose description says that
>> > it lets differentiate between sync and async exceptions, in a certain
>> > sense) doesn't help you -- you simply don't want any exceptions at all.
>> >
>> > My use case is simpler -- I write testing libraries. If a test throws an
>> > exception, we have to decide whether we want to report it as a test's
>> > failure or it's a bigger problem and we want to wrap up.
>> >
>> > I don't think there's a universally right way to make this decision. It
>> > depends on what exceptions exist and what threads they can be thrown to.
>> > E.g. if there existed something like UserInterrupt but which could be
>> > thrown to any active thread, not only the main thread, then the approach
>> > "run in a separate thread and log any exceptions from that thread"
>> > simply wouldn't work.
>> >
>> > For tasty, based on the async exceptions I'm aware of, I think your
>> > approach is overall better. It's almost as simple, doesn't require
>> > patching 3rd-party timeout libraries, and catches StackOverflow (which
>> > is desirable). So I'll switch to it instead.
>> >
>> > For smallcheck, the overhead of forkIO might be significant, because it
>> > has to be performed for every single property check, and those can be
>> > numerous and very quick. I put together a simple benchmark
>> > (http://lpaste.net/99532 if anyone is interested) which shows that
>> > overhead can be noticable (16% for async vs 4% for simple catch) but
>> > tolerable, and it will be even less for more realistic properties.
>> > So I'll probably use the async approach there, too, although I may
>> > reconsider that in the future if I ever get to optimizing smallcheck and
>> > squeezing out those percents.
>> >
>> > As for the package itself, let's see if others will find any good use
>> > cases for it. I'll update the docs with some conclusions from this
>> > thread.
>> >
>> > And thanks for your input.
>> >
>> > Roman
>> >
>> > * Michael Snoyman <michael at snoyman.com> [2014-02-05 18:48:22+0200]
>> >> I can't think of any situation in which the semantics you're implying make
>> >> sense. To me, catching synchronous exception is a simple concept: if an
>> >> exception is generated internally to `userAction`, then it's a synchronous
>> >> exception. If it was terminated by something external, then it's
>> >> asynchronous. I'm not sure what you're getting at about my approach
>> >> requiring knowledge of what's going on deep inside a library.
>> >>
>> >> The real question which is not explained in your package is what use case
>> >> you're actually trying to address. Here's a prime example I've run into:
>> >> you're writing a web application which uses a third-party library. If that
>> >> library throws an exception of any type, you want to catch the exception
>> >> and display an appropriate error message (or perhaps return some data from
>> >> another source). However, we still want the web application to respect
>> >> timeout messages from the server to avoid slowloris attacks. The handler
>> >> code would look like:
>> >>
>> >> myHandler = do
>> >>     eres <- tryAnyDeep someLibraryFunction
>> >>     case eres of
>> >>         Left e -> tellUser "I'm sorry, there was an issue making the query"
>> >>         Right x -> displayData x
>> >>
>> >> The goal is that, under no circumstances, should someLibraryFunction be
>> >> able to case the exception to escape tryAnyDeep. This includes rethrowing
>> >> some async exception that it received from, e.g., a timeout. This would not
>> >> be honored by trySync.
>> >>
>> >> Michael
>> >
>> > _______________________________________________
>> > Haskell-Cafe mailing list
>> > Haskell-Cafe at haskell.org
>> > http://www.haskell.org/mailman/listinfo/haskell-cafe
>> >


More information about the Haskell-Cafe mailing list