safe-exceptions 0.1.4.0 → 0.1.5.0
raw patch · 5 files changed
+95/−41 lines, 5 filesdep ~base
Dependency ranges changed: base
Files
- ChangeLog.md +5/−0
- README.md +12/−37
- safe-exceptions.cabal +1/−1
- src/Control/Exception/Safe.hs +69/−3
- test/Control/Exception/SafeSpec.hs +8/−0
ChangeLog.md view
@@ -1,3 +1,8 @@+## 0.1.5.0++* Re-export `Control.Exception.assert`+* Add `throwString`+ ## 0.1.4.0 * Add `catchJust`, `handleJust`, and `tryJust`
README.md view
@@ -2,19 +2,18 @@ *Safe, consistent, and easy exception handling* -__NOTE__: This library is early in its development, and there may be some-changes in the near future. See [possible future-changes](#possible-future-changes).- [](https://travis-ci.org/fpco/safe-exceptions) Runtime exceptions - as exposed in `base` by the `Control.Exception` module - have long been an intimidating part of the Haskell ecosystem. This package, and this README for the package, are intended-to overcome this. By providing an API that encourages best practices,-and explaining the corner cases clearly, the hope is to turn what was-previously something scary into an aspect of Haskell everyone feels-safe using.+to overcome this. It provides a safe and simple API on top of the+existing exception handling machinery. The API is equivalent to the+underlying implementation in terms of power but encourages best+practices to minimize the chances of getting the exception handling+wrong. By doing so and explaining the corner cases clearly, the hope is+to turn what was previously something scary into an aspect of Haskell+everyone feels safe using. ## Goals @@ -71,6 +70,9 @@ 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).+There is also an+[exception safety tutorial on haskell-lang.org](https://haskell-lang.org/tutorial/exception-safety)+which is based on this package. ## Terminology @@ -165,8 +167,8 @@ Therefore, this package takes the approach of trusting type information to determine if an exception is asynchronous or synchronous. The details are less interesting to a user, but the-basics are: we leverage the extensible extension system in GHC and-state that any extension type which is a child of `SomeAsyncException`+basics are: we leverage the extensible exception system in GHC and+state that any exception type which is a child of `SomeAsyncException` is an async exception. All other exception types are assumed to be synchronous. @@ -321,30 +323,3 @@ kill signal from another thread or the user (via Ctrl-C), we would like to be able to detect that we entered a deadlock condition and do something intelligent in an application.--## Possible future changes--### Interruptible vs uninterruptible masking--This discussion is now being tracked at:-https://github.com/fpco/safe-exceptions/issues/3--In `Control.Exception`, allocation functions and cleanup handlers in-combinators like `bracket` are masked using the (interruptible) `mask`-function, in contrast to `uninterruptibleMask`. There have been some debates-about the correctness of this in the past, notably [a libraries mailing list-discussion kicked off by Eyal-Lotem](https://mail.haskell.org/pipermail/libraries/2014-September/023675.html).-It seems that general consensus is:--* `uninterruptibleMask` is a better choice-* But changing the core library like this would potentially break too many- programs--In its current version, this library uses `mask` (interruptible) for allocation-functions and `uninterruptibleMask` cleanup handlers. This is a debatable-decision (and one worth debating!). An example of alternatives would be:--* Use `uninterruptibleMask` for both allocation and cleanup pieces-* Match `Control.Exception`'s behavior-* Provide two versions of each function, or possibly two modules
safe-exceptions.cabal view
@@ -1,5 +1,5 @@ name: safe-exceptions-version: 0.1.4.0+version: 0.1.5.0 synopsis: Safe, consistent, and easy exception handling description: Please see README.md homepage: https://github.com/fpco/safe-exceptions#readme
src/Control/Exception/Safe.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ImplicitParams #-} -- | Please see the README.md file in the safe-exceptions repo for -- information on how to use this module. Relevant links: --@@ -13,6 +14,8 @@ throw , throwIO , throwM+ , throwString+ , StringException (..) , throwTo , impureThrow -- * Catching (with recovery)@@ -77,6 +80,7 @@ , SomeException (..) , SomeAsyncException (..) , E.IOException+ , E.assert #if !MIN_VERSION_base(4,8,0) , displayException #endif@@ -91,6 +95,11 @@ import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Typeable (Typeable, cast) +#if MIN_VERSION_base(4,9,0)+import GHC.Stack (prettySrcLoc)+import GHC.Stack.Types (HasCallStack, CallStack, getCallStack)+#endif+ -- | Synchronously throw the given exception -- -- @since 0.1.0.0@@ -109,8 +118,65 @@ throwM :: (C.MonadThrow m, Exception e) => e -> m a throwM = throw --- | Throw an asynchronous exception to another thread+-- | A convenience function for throwing a user error. This is useful+-- for cases where it would be too high a burden to define your own+-- exception type. --+-- This throws an exception of type 'StringException'. When GHC+-- supports it (base 4.9 and GHC 8.0 and onward), it includes a call+-- stack.+--+-- @since 0.1.5.0+#if MIN_VERSION_base(4,9,0)+throwString :: (C.MonadThrow m, HasCallStack) => String -> m a+throwString s = throwM (StringException s ?callStack)+#else+throwString :: C.MonadThrow m => String -> m a+throwString s = throwM (StringException s ())+#endif++-- | Exception type thrown by 'throwString'.+--+-- Note that the second field of the data constructor depends on+-- GHC/base version. For base 4.9 and GHC 8.0 and later, the second+-- field is a call stack. Previous versions of GHC and base do not+-- support call stacks, and the field is simply unit (provided to make+-- pattern matching across GHC versions easier).+--+-- @since 0.1.5.0+#if MIN_VERSION_base(4,9,0)+data StringException = StringException String CallStack+ deriving Typeable++instance Show StringException where+ show (StringException s cs) = concat+ $ "Control.Exception.Safe.throwString called with:\n\n"+ : s+ : "\nCalled from:\n"+ : map go (getCallStack cs)+ where+ go (x, y) = concat+ [ " "+ , x+ , " ("+ , prettySrcLoc y+ , ")\n"+ ]+#else+data StringException = StringException String ()+ deriving Typeable++instance Show StringException where+ show (StringException s _) = "Control.Exception.Safe.throwString called with:\n\n" ++ s+#endif+instance Exception StringException++-- | Throw an asynchronous exception to another thread.+--+-- Synchronously typed exceptions will be wrapped into an+-- `AsyncExceptionWrapper`, see+-- <https://github.com/fpco/safe-exceptions#determining-sync-vs-async>+-- -- It's usually a better idea to use the async package, see -- <https://github.com/fpco/safe-exceptions#quickstart> --@@ -325,7 +391,7 @@ C.try $ C.uninterruptibleMask_ $ after x C.throwM e1 Right y -> do- C.uninterruptibleMask_ $ after x+ _ <- C.uninterruptibleMask_ $ after x return y -- | Async safe version of 'E.bracket_'@@ -346,7 +412,7 @@ _ :: Either SomeException b <- C.try after C.throwM e1 Right x -> do- after+ _ <- after return x -- | Async safe version of 'E.bracketOnError'
test/Control/Exception/SafeSpec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ScopedTypeVariables #-} module Control.Exception.SafeSpec (spec) where@@ -12,6 +13,9 @@ import System.IO.Unsafe (unsafePerformIO) import System.Timeout (timeout) import Test.Hspec+#if !MIN_VERSION_base(4,9,0)+import System.IO.Error (isUserError)+#endif newtype ExceptionPred = ExceptionPred { getExceptionPred :: Maybe () } deriving (Show, Eq, Typeable) @@ -124,3 +128,7 @@ let ex = ExceptionPred Nothing res <- try (catchJust getExceptionPred (throw ex) (return . Just)) res `shouldBe` Left ex++ describe "throwString" $ do+ it "is a StringException" $+ throwString "foo" `catch` \(StringException _ _) -> return () :: IO ()