transient-haskell / transient

A full stack, reactive architecture for general purpose programming. Algebraic and monadically composable primitives for concurrency, parallelism, event handling, transactions, multithreading, Web, and distributed computing with complete de-inversion of control (No callbacks, no blocking, pure state)
MIT License
630 stars 28 forks source link

Lost or silently ignored exceptions #61

Closed harendra-kumar closed 6 years ago

harendra-kumar commented 7 years ago

In the current implementation if there is no transient level exception handler installed we just silently ignore the IO exception. Consider this simple program:

main = keep $ liftIO $ error "Raised ErrorCall exception" >> return ()

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 the back function for relevant code.

harendra-kumar commented 7 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?

agocorona commented 7 years ago

Yes. It is necessary a SomeException handler at the top level defined in keep that prints the error

agocorona commented 7 years ago

@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 silentthat 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"

jberryman commented 7 years ago

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.

agocorona commented 7 years ago

@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.