
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
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
[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
: 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
[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@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe