Closed harendra-kumar closed 6 years ago
Failure indication in transient is done via empty
or stop
which makes the computation return a Nothing
value. So that way the exception is not really lost because we use stop
even when there is no exception installed. And we can always check the return value to know whether the computation failed or succeeded. Though it is pretty silent. Is there a way around that?
Yes. It is necessary a SomeException
handler at the top level defined in keep
that prints the error
@harendra-kumar do you ask that question in the context of exceptions or in general?
I see that hard-coding a top exception handler that print every exception in keep
is not a good idea, since some exceptions may pollute the application with unnecessary messages.
I think that the programmer can add one If he wish. For example:
keep $ do
onException $\(e:: SomeException) -> liftIO $ print e
code
That should print any exception that either has not been handled or that has backtracked to the top (in the second case, it has not been redirected forward with continue
)
In general, whether talking about exceptions or not, a more loudier way to fail is possible by using <|>
. If we have a silent failing code silent
that return empty
then the loudier fail can be constructed:
silent <|> error "hey, silent failed"
In this case it is an error, but it may be anything, for example a meaningful value or whatever. That makes silent
more modular and more composable and useful in more contexts, since the meaning and the treatment of the failure of silent
is interpreted by the context upon which it depends.
Here i assume that silent
is synchronous, since asynchronous primitives ever return empty
, in an asynchronous context, empty
means "I fail because my real work is done in other threads, bye"
The approach taken by async
is I think the right one with respect to exceptions and forked threads. I'm not sure how that would map to the DSL y'all use here.
@jberryman do you mean the async library?
It is right for that library. It has applicative composition. Transient has monadic composition too. In transient the handlers are stored in the state of the monad and are applied to every new thread. When an exception happens, it executes every of these handlers in reverse order, unless continue
stop this process and continue the execution normally.
Suppose this code in transient:
do
....
onException $ \(e :: SomeException) -> liftIO (print e) >> continue
data <- oneThread $ (,) <$> async download1 <*> async dowmload2
...
If either the first async thread of the second fail, this threads executes the exception code, and continue
will force the re-execution of the download again. oneThread
kill all previous threads that have passed trough it, so the other async is killed before spawning a new pair of threads (ThreadKilled
exception should be filtered out in this case)
That onException
is not the one of Control.Monad.Exception, but of Transient.Internals, which is different.
In the current implementation if there is no transient level exception handler installed we just silently ignore the IO exception. Consider this simple program:
Instead we should re-raise the exception if there is no handler installed, so that it does not go unnoticed. See the
MonadIO
instance and theback
function for relevant code.