packages feed

safe-exceptions 0.1.0.0 → 0.1.1.0

raw patch · 6 files changed

+154/−20 lines, 6 filesdep +deepseq

Dependencies added: deepseq

Files

+ COOKBOOK.md view
@@ -0,0 +1,53 @@+This is a cookbook for the usage of `safe-exceptions`. You should+start off by+[reading the README](https://github.com/fpco/safe-exceptions#readme),+or at least+[the quickstart section](https://github.com/fpco/safe-exceptions#quickstart).++_Request to readers_: if there are specific workflows that you're+unsure of how to accomplish with this library, please ask so we can+add them here. Issues and pull requests very much welcome!++## User-defined async exceptions++In order to define an async exception, you must leverage the+extensible exception machinery, as demonstrated below. Try running the+program, and then comment out the implementation of `toException` and+`fromException` to see the difference in behavior.++```haskell+#!/usr/bin/env stack+-- stack --resolver lts-6.4 runghc --package safe-exceptions-0.1.0.0+import Control.Concurrent     (forkIO, newEmptyMVar, putMVar, takeMVar,+                               threadDelay)+import Control.Exception.Safe+import Data.Typeable          (Typeable, cast)++data MyAsyncException = MyAsyncException+    deriving (Show, Typeable)++instance Exception MyAsyncException where+    toException = toException . SomeAsyncException+    fromException se = do+        SomeAsyncException e <- fromException se+        cast e++main :: IO ()+main = do+    baton <- newEmptyMVar -- give the handler a chance to run+    tid <- forkIO $ threadDelay maxBound+        `withException`+            (\e -> print ("Inside withException", e :: MyAsyncException))+        `finally` putMVar baton ()+    throwTo tid MyAsyncException+    takeMVar baton+    putStrLn "Done!"+```++The reason the `Inside withException` message isn't printed without+the implementation of `toException` and `fromException` given above is+that `throwTo` wraps `MyAsyncException` inside a different async+exception type, which foils the exception handler from firing.++*NOTE*: The above code is _not_ recommended concurrency code. If you+have to do something like this, _please use the async package_.
ChangeLog.md view
@@ -1,3 +1,10 @@+## 0.1.1.0++* Add missing `toSyncException` inside `impureThrow`+* Conditionally export `displayException` for older GHCs+* Re-export `Typeable`+* Add the deepseq variants of catch/handle/try functions+ ## 0.1.0.0  * Initial releae
README.md view
@@ -4,7 +4,7 @@  __NOTE__: This library is early in its development, and there may be some changes in the near future. See [possible future-changes](-exceptions#possible-future-changes).+changes](#possible-future-changes).  [![Build Status](https://travis-ci.org/fpco/safe-exceptions.svg?branch=master)](https://travis-ci.org/fpco/safe-exceptions) @@ -59,6 +59,7 @@ * If you need to perform some allocation or cleanup of resources, use   one of the following functions (and _don't_ use the   `catch`/`handle`/`try` family of functions):+     * `onException`     * `withException`     * `bracket`@@ -67,11 +68,9 @@     * `bracketOnError`     * `bracketOnError_` -Hopefully this will be able to get you up-and-running quickly.--_Request to readers_: if there are specific workflows that you're-unsure of how to accomplish with this library, please ask so we can-develop a more full-fledged cookbook as a companion to this file.+Hopefully this will be able to get you up-and-running quickly. You may+also be interested in+[browsing through the cookbook](https://github.com/fpco/safe-exceptions/blob/master/COOKBOOK.md).  ## Terminology @@ -147,7 +146,7 @@   certain exception types are only thrown synchronously and certain   only asynchronously. -Both of these approaches have downsides. For the downsides the+Both of these approaches have downsides. For the downsides of the type-based approach, see the caveats section at the end. The problems with the first are more interesting to us here: @@ -227,7 +226,7 @@ * The `catch` function in this package will not catch async   exceptions. Please use `catchAsync` if you really want to catch   those, though it's usually better to use a function like `bracket`-  or `withException` with ensure that the thrown exception is+  or `withException` which ensure that the thrown exception is   rethrown.  ## Caveats@@ -258,7 +257,7 @@ exceptions in a sum type.  This library isn't meant to settle the debate on checked vs unchecked,-by rather to bring sanity to Haskell's runtime exception system. As+but rather to bring sanity to Haskell's runtime exception system. As such, this library is decidedly in the unchecked exception camp, purely by virtue of the fact that the underlying mechanism is as well. @@ -320,7 +319,7 @@ * By our standards of recoverable vs non-recoverable, these exceptions   certainly fall into the recoverable category. Unlike an intentional   kill signal from another thread or the user (via Ctrl-C), we would-  like to be able to detect the we entered a deadlock condition and do+  like to be able to detect that we entered a deadlock condition and do   something intelligent in an application.  ## Possible future changes@@ -349,8 +348,3 @@ * Use `uninterruptibleMask` for both allocation and cleanup pieces * Match `Control.Exception`'s behavior * Provide two versions of each function, or possibly two modules--### Naming of the synchronous monadic throwing function--We may decide to rename `throw` to something else at some point. Please see-https://github.com/fpco/safe-exceptions/issues/4
safe-exceptions.cabal view
@@ -1,8 +1,8 @@ name:                safe-exceptions-version:             0.1.0.0+version:             0.1.1.0 synopsis:            Safe, consistent, and easy exception handling description:         Please see README.md-homepage:            https://github.com/githubuser/safe-exceptions#readme+homepage:            https://github.com/fpco/safe-exceptions#readme license:             MIT license-file:        LICENSE author:              Michael Snoyman@@ -10,13 +10,14 @@ copyright:           2016 FP Complete category:            Control build-type:          Simple-extra-source-files:  README.md ChangeLog.md+extra-source-files:  README.md ChangeLog.md COOKBOOK.md cabal-version:       >=1.10  library   hs-source-dirs:      src   exposed-modules:     Control.Exception.Safe   build-depends:       base >= 4.7 && < 4.10+                     , deepseq >= 1.2 && < 1.5                      , exceptions >= 0.8 && < 0.9                      , transformers >= 0.2 && < 0.6   default-language:    Haskell2010
src/Control/Exception/Safe.hs view
@@ -18,14 +18,20 @@       -- * Catching (with recovery)     , catch     , catchAny+    , catchDeep+    , catchAnyDeep     , catchAsync      , handle     , handleAny+    , handleDeep+    , handleAnyDeep     , handleAsync      , try     , tryAny+    , tryDeep+    , tryAnyDeep     , tryAsync        -- * Cleanup (no recovery)@@ -57,12 +63,17 @@     -- FIXME , C.tryIOError     , C.Handler (..)     , Exception (..)+    , Typeable     , SomeException (..)     , SomeAsyncException (..)     , E.IOException+#if !MIN_VERSION_base(4,8,0)+    , displayException+#endif     ) where  import Control.Concurrent (ThreadId)+import Control.DeepSeq (($!!), NFData) import Control.Exception (Exception (..), SomeException (..), SomeAsyncException (..)) import qualified Control.Exception as E import qualified Control.Monad.Catch as C@@ -105,9 +116,10 @@ -- -- @since 0.1.0.0 impureThrow :: Exception e => e -> a-impureThrow = E.throw+impureThrow = E.throw . toSyncException --- | Flipped version of 'catch'+-- | Same as upstream 'C.catch', but will not catch asynchronous+-- exceptions -- -- @since 0.1.0.0 catch :: (C.MonadCatch m, Exception e) => m a -> (e -> m a) -> m a@@ -124,6 +136,26 @@ catchAny :: C.MonadCatch m => m a -> (SomeException -> m a) -> m a catchAny = catch +-- | Same as 'catch', but fully force evaluation of the result value+-- to find all impure exceptions.+--+-- @since 0.1.1.0+catchDeep :: (C.MonadCatch m, MonadIO m, Exception e, NFData a)+          => m a -> (e -> m a) -> m a+catchDeep = catch . evaluateDeep++-- | Internal helper function+evaluateDeep :: (MonadIO m, NFData a) => m a -> m a+evaluateDeep action = do+    res <- action+    liftIO (E.evaluate $!! res)++-- | 'catchDeep' specialized to catch all synchronous exception+--+-- @since 0.1.1.0+catchAnyDeep :: (C.MonadCatch m, MonadIO m, NFData a) => m a -> (SomeException -> m a) -> m a+catchAnyDeep = catchDeep+ -- | 'catch' without async exception safety -- -- Generally it's better to avoid using this function since we do not want to@@ -146,6 +178,18 @@ handleAny :: C.MonadCatch m => (SomeException -> m a) -> m a -> m a handleAny = flip catchAny +-- | Flipped version of 'catchDeep'+--+-- @since 0.1.1.0+handleDeep :: (C.MonadCatch m, Exception e, MonadIO m, NFData a) => (e -> m a) -> m a -> m a+handleDeep = flip catchDeep++-- | Flipped version of 'catchAnyDeep'+--+-- @since 0.1.1.0+handleAnyDeep :: (C.MonadCatch m, MonadIO m, NFData a) => (SomeException -> m a) -> m a -> m a+handleAnyDeep = flip catchAnyDeep+ -- | Flipped version of 'catchAsync' -- -- Generally it's better to avoid using this function since we do not want to@@ -169,6 +213,19 @@ tryAny :: C.MonadCatch m => m a -> m (Either SomeException a) tryAny = try +-- | Same as 'try', but fully force evaluation of the result value+-- to find all impure exceptions.+--+-- @since 0.1.1.0+tryDeep :: (C.MonadCatch m, MonadIO m, E.Exception e, NFData a) => m a -> m (Either e a)+tryDeep f = catch (liftM Right (evaluateDeep f)) (return . Left)++-- | 'tryDeep' specialized to catch all synchronous exceptions+--+-- @since 0.1.1.0+tryAnyDeep :: (C.MonadCatch m, MonadIO m, NFData a) => m a -> m (Either SomeException a)+tryAnyDeep = tryDeep+ -- | 'try' without async exception safety -- -- Generally it's better to avoid using this function since we do not want to@@ -343,3 +400,13 @@ isAsyncException :: Exception e => e -> Bool isAsyncException = not . isSyncException {-# INLINE isAsyncException #-}++#if !MIN_VERSION_base(4,8,0)+-- | A synonym for 'show', specialized to 'Exception' instances.+--+-- Starting with base 4.8, the 'Exception' typeclass has a method @displayException@, used for user-friendly display of exceptions. This function provides backwards compatibility for users on base 4.7 and earlier, so that anyone importing this module can simply use @displayException@.+--+-- @since 0.1.1.0+displayException :: Exception e => e -> String+displayException = show+#endif
test/Control/Exception/SafeSpec.hs view
@@ -93,3 +93,15 @@         describe "bracket_" $ withPairs $ \e1 e2 -> bracket_ (return ()) e2 e1         describe "finally" $ withPairs $ \e1 e2 -> e1 `finally` e2         describe "bracketOnError_" $ withPairs $ \e1 e2 -> bracketOnError_ (return ()) e2 e1++    describe "deepseq" $ do+        describe "catchAnyDeep" $ withAll $ \e _ -> do+            res <- return (impureThrow e) `catchAnyDeep` \_ -> return ()+            res `shouldBe` ()+        describe "handleAnyDeep" $ withAll $ \e _ -> do+            res <- handleAnyDeep (const $ return ()) (return (impureThrow e))+            res `shouldBe` ()+        describe "tryAnyDeep" $ withAll $ \e _ -> do+            res <- tryAnyDeep (return (impureThrow e))+            -- deal with a missing NFData instance+            shouldBeSync $ either Left (\() -> Right undefined) res