Safe Haskell | Safe-Inferred |
---|

Exception handling and resource management integrated with proxies

- module Control.Proxy.Trans.Either
- module Control.Exception
- type ExceptionP = EitherP SomeException
- throw :: (Monad m, Proxy p, Exception e) => e -> ExceptionP p a' a b' b m r
- catch :: (Exception e, Monad m, Proxy p) => ExceptionP p a' a b' b m r -> (e -> ExceptionP p a' a b' b m r) -> ExceptionP p a' a b' b m r
- handle :: (Exception e, Monad m, Proxy p) => (e -> ExceptionP p a' a b' b m r) -> ExceptionP p a' a b' b m r -> ExceptionP p a' a b' b m r
- data SafeIO r
- runSafeIO :: SafeIO (Either SomeException r) -> IO r
- runSaferIO :: SafeIO (Either SomeException r) -> IO r
- trySafeIO :: SafeIO e -> IO e
- trySaferIO :: SafeIO e -> IO e
- class Proxy p => CheckP p where
- try :: p a' a b' b IO r -> ExceptionP p a' a b' b SafeIO r

- tryK :: CheckP p => (q -> p a' a b' b IO r) -> q -> ExceptionP p a' a b' b SafeIO r
- tryIO :: Proxy p => IO r -> ExceptionP p a' a b' b SafeIO r
- onAbort :: (Monad m, Proxy p) => (forall x. SafeIO x -> m x) -> IO r' -> ExceptionP p a' a b' b m r -> ExceptionP p a' a b' b m r
- finally :: (Monad m, Proxy p) => (forall x. SafeIO x -> m x) -> IO r' -> ExceptionP p a' a b' b m r -> ExceptionP p a' a b' b m r
- bracket :: (Monad m, Proxy p) => (forall x. SafeIO x -> m x) -> IO h -> (h -> IO r') -> (h -> ExceptionP p a' a b' b m r) -> ExceptionP p a' a b' b m r
- bracket_ :: (Monad m, Proxy p) => (forall x. SafeIO x -> m x) -> IO r1 -> IO r2 -> ExceptionP p a' a b' b m r -> ExceptionP p a' a b' b m r
- bracketOnAbort :: (Monad m, Proxy p) => (forall x. SafeIO x -> m x) -> IO h -> (h -> IO r') -> (h -> ExceptionP p a' a b' b m r) -> ExceptionP p a' a b' b m r
- unsafeCloseU :: Proxy p => r -> ExceptionP p a' a b' b SafeIO r
- unsafeCloseD :: Proxy p => r -> ExceptionP p a' a b' b SafeIO r
- unsafeClose :: Proxy p => r -> ExceptionP p a' a b' b SafeIO r

# Exception Handling

This library checks and stores all exceptions using the `EitherP`

proxy
transformer. The `ExceptionP`

type synonym simplifies type signatures.

Use `runEitherP`

/ `runEitherK`

from the re-exported
`Control.Proxy.Trans.Either`

to convert `ExceptionP`

back to the base
monad

This module does not re-export `throw`

, `catch`

, and `handle`

from
`Control.Proxy.Trans.Either`

and instead defines new versions similar to the
API from `Control.Exception`

. If you want the old versions you will need to
import them qualified.

This module only re-exports `SomeException`

and `Exception`

from
`Control.Exception`

.

module Control.Proxy.Trans.Either

module Control.Exception

type ExceptionP = EitherP SomeExceptionSource

A proxy transformer that stores exceptions using `EitherP`

throw :: (Monad m, Proxy p, Exception e) => e -> ExceptionP p a' a b' b m rSource

Analogous to `throwIO`

from `Control.Exception`

:: (Exception e, Monad m, Proxy p) | |

=> ExceptionP p a' a b' b m r | Original computation |

-> (e -> ExceptionP p a' a b' b m r) | Handler |

-> ExceptionP p a' a b' b m r |

Analogous to `catch`

from `Control.Exception`

:: (Exception e, Monad m, Proxy p) | |

=> (e -> ExceptionP p a' a b' b m r) | Handler |

-> ExceptionP p a' a b' b m r | Original computation |

-> ExceptionP p a' a b' b m r |

Analogous to `handle`

from `Control.Exception`

# Safe IO

runSaferIO :: SafeIO (Either SomeException r) -> IO rSource

Convert back to the `IO`

monad, running all dropped finalizers at the very
end and rethrowing any checked exceptions

This uses `uninterruptibleMask`

to mask asynchronous exceptions and only
unmasks them during `try`

or `tryIO`

.

trySaferIO :: SafeIO e -> IO eSource

Convert back to the `IO`

monad, running all dropped finalizers at the very
end and preserving exceptions as `Left`

s

This uses `uninterruptibleMask`

to mask asynchronous exceptions and only
unmasks them during `try`

or `tryIO`

.

# Checked Exceptions

The following `try`

functions are the only way to convert `IO`

actions to
`SafeIO`

. These functions check all exceptions, including asynchronous
exceptions, and store them in the `ExceptionP`

proxy transformer.

class Proxy p => CheckP p whereSource

Use `try`

to retroactively check all exceptions for proxies that implement
`CheckP`

.

`try`

is *almost* a proxy morphism (See `Control.Proxy.Morph`

from `pipes`

for the full list of laws). The only exception is the following law:

try (return x) = return x

The left-hand side unmasks asynchronous exceptions and checks them
immediately, whereas the right-hand side delays asynchronous exceptions
until the next `try`

or `tryIO`

block.

try :: p a' a b' b IO r -> ExceptionP p a' a b' b SafeIO rSource

tryK :: CheckP p => (q -> p a' a b' b IO r) -> q -> ExceptionP p a' a b' b SafeIO rSource

Check all exceptions for a `Proxy`

'`K`

'leisli arrow

# Finalization

:: (Monad m, Proxy p) | |

=> (forall x. SafeIO x -> m x) | Monad morphism |

-> IO r' | Action to run on abort |

-> ExceptionP p a' a b' b m r | Guarded computation |

-> ExceptionP p a' a b' b m r |

Similar to `onException`

from `Control.Exception`

, except this also
protects against:

- premature termination, and
- exceptions in other proxy stages.

The first argument lifts `onAbort`

to work with other base monads. Use
`id`

if your base monad is already `SafeIO`

.

`(onAbort morph fin)`

is a monad morphism:

onAbort morph fin $ do x <- m = do x <- onAbort morph fin m f x onAbort morph fin (f x) onAbort morph fin (return x) = return x

`onAbort`

ensures finalizers are called from inside to out:

onAbort morph fin1 . onAbort morph fin2 = onAbort morph (fin2 >> fin1) onAbort morph (return ()) = id

:: (Monad m, Proxy p) | |

=> (forall x. SafeIO x -> m x) | Monad morphism |

-> IO r' | Guaranteed final action |

-> ExceptionP p a' a b' b m r | Guarded computation |

-> ExceptionP p a' a b' b m r |

:: (Monad m, Proxy p) | |

=> (forall x. SafeIO x -> m x) | Monad morphism |

-> IO h | Acquire resource |

-> (h -> IO r') | Release resource |

-> (h -> ExceptionP p a' a b' b m r) | Use resource |

-> ExceptionP p a' a b' b m r |

Analogous to `bracket`

from `Control.Exception`

The first argument lifts `bracket`

to work with other base monads. Use `id`

if your base monad is already `SafeIO`

.

`bracket`

guarantees that if the resource acquisition completes, then the
resource will be released.

bracket morph before after p = do h <- hoist morph $ tryIO before finally morph (after h) (p h)

:: (Monad m, Proxy p) | |

=> (forall x. SafeIO x -> m x) | Monad morphism |

-> IO r1 | Acquire resource |

-> IO r2 | Release resource |

-> ExceptionP p a' a b' b m r | Use resource |

-> ExceptionP p a' a b' b m r |

:: (Monad m, Proxy p) | |

=> (forall x. SafeIO x -> m x) | Monad morphism |

-> IO h | Acquire resource |

-> (h -> IO r') | Release resource |

-> (h -> ExceptionP p a' a b' b m r) | Use resource |

-> ExceptionP p a' a b' b m r |

Analogous to `bracketOnError`

from `Control.Exception`

The first argument lifts `bracketOnAbort`

to work with any base monad. Use
`id`

if your base monad is already `SafeIO`

.

bracketOnAbort morph before after p = do h <- hoist morph $ tryIO before onAbort morph (after h) (p h)

# Prompt Finalization

This implementation will not *promptly* finalize a `Proxy`

if another
composed `Proxy`

prematurely terminates. However, the implementation will
still save the dropped finalizer and run it when the `Session`

completes
in order to guarantee deterministic finalization.

To see why, consider the following `Proxy`

assembly:

p1 >-> ((p2 >-> p3) >=> p4)

Now ask yourself the question, "If `p3`

prematurely terminates, should it
promptly finalize `p1`

?"

If you answered "yes", then you would have a bug if `p4`

were to
`request`

, which would restore control to `p1`

after we already finalized
it.

If you answered "no", then consider the case where `p2 = idT`

and
`p4 = return`

:

p1 >-> ((idT >-> p3) >=> return) p1 >-> (idT >-> p3) -- f >=> return = f p1 >-> p3 -- idT >-> p = p

Answering "no" means that `p3`

would be unable to promptly finalize a
`Proxy`

immediately upstream of it.

There is a solution that permits perfectly prompt finalization, but it
requires indexed monads to guarantee the necessary safety through the type
system. In the absence of indexed monads, the next best solution is to let
you promptly finalize things yourself, but then *you* must prove that this
finalization is safe and that all upstream pipes are unreachable.

The following two unsafe operations allow you to trade safety for prompt finalization. Use them if you desire prompter finalization guarantees and if you can prove their usage is safe. However, this proof is not trivial.

For example, you might suppose that the following usage of `unsafeCloseU`

is
safe because it never `request`

s after closing upstream, nor does it
terminate:

falseSenseOfSecurity () = do x <- request () unsafeCloseU () forever $ respond x

However, this is not safe, as the following counter-example demonstrates:

p1 >-> ((falseSenseOfSecurity >-> request) >=> request)

`falseSenseOfSecurity`

will finalize the upstream `p1`

, but then will
abort when the downstream `request`

terminates, and then the second
`request`

will illegally access `p1`

after we already finalized it.

In other words, you cannot prove any prompt finalization is safe unless you
control the entire `Session`

. Therefore, do not use the following unsafe
operations in `Proxy`

libraries. Only the end user assembling the
final `Session`

may safely insert these calls.

unsafeCloseU :: Proxy p => r -> ExceptionP p a' a b' b SafeIO rSource

`unsafeCloseU`

calls all finalizers registered upstream of the current
`Proxy`

.

unsafeCloseD :: Proxy p => r -> ExceptionP p a' a b' b SafeIO rSource

`unsafeCloseD`

calls all finalizers registered downstream of the current
`Proxy`

.

unsafeClose :: Proxy p => r -> ExceptionP p a' a b' b SafeIO rSource

`unsafeClose`

calls all registered finalizers

`unsafeClose`

is a Kleisli arrow so that you can easily seal terminating
proxies if there is a risk of delayed finalization:

(producer >-> (takeB_ 10 >=> unsafeClose) >-> consumer) >=> later