Skip to content

Commit a39ae24

Browse files
delfigamerEvgeny Osipenko
authored and
Evgeny Osipenko
committed
change functions to use generalBracket where appropriate
1 parent 9d8baf8 commit a39ae24

File tree

5 files changed

+102
-47
lines changed

5 files changed

+102
-47
lines changed

ChangeLog.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# ChangeLog for safe-exceptions
22

3+
## 0.1.7.2
4+
5+
* Changed `bracketWithError` and `bracketOnError` to use `generalBracket` from `MonadMask` [#36](https://github.com/fpco/safe-exceptions/issues/36)
6+
* Raised dependency `exceptions` from `>= 0.8` to `>= 0.10`
7+
38
## 0.1.7.1
49

510
* Doc update

safe-exceptions.cabal

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: safe-exceptions
2-
version: 0.1.7.1
2+
version: 0.1.7.2
33
synopsis: Safe, consistent, and easy exception handling
44
description: Please see README.md
55
homepage: https://github.com/fpco/safe-exceptions#readme
@@ -18,7 +18,7 @@ library
1818
exposed-modules: Control.Exception.Safe
1919
build-depends: base >= 4.7 && < 5
2020
, deepseq >= 1.2 && < 1.5
21-
, exceptions >= 0.8 && < 0.11
21+
, exceptions >= 0.10 && < 0.11
2222
, transformers >= 0.2 && < 0.6
2323
default-language: Haskell2010
2424

@@ -30,6 +30,7 @@ test-suite safe-exceptions-test
3030
build-depends: base
3131
, hspec
3232
, safe-exceptions
33+
, transformers
3334
, void
3435
ghc-options: -threaded -rtsopts -with-rtsopts=-N
3536
default-language: Haskell2010

src/Control/Exception/Safe.hs

+35-43
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ import Control.Exception (Exception (..), SomeException (..), SomeAsyncException
9393
import qualified Control.Exception as E
9494
import qualified Control.Monad.Catch as C
9595
import Control.Monad.Catch (Handler (..))
96-
import Control.Monad (liftM)
96+
import Control.Monad (liftM, void)
9797
import Control.Monad.IO.Class (MonadIO, liftIO)
9898
import Data.Typeable (Typeable, cast)
9999

@@ -366,13 +366,12 @@ onException thing after = withException thing (\(_ :: SomeException) -> after)
366366
-- @since 0.1.0.0
367367
withException :: (C.MonadMask m, E.Exception e) => m a -> (e -> m b) -> m a
368368
withException thing after = C.uninterruptibleMask $ \restore -> do
369-
res1 <- C.try $ restore thing
370-
case res1 of
371-
Left e1 -> do
372-
-- see explanation in bracket
373-
_ :: Either SomeException b <- C.try $ after e1
374-
C.throwM e1
375-
Right x -> return x
369+
fmap fst $ C.generalBracket (pure ()) cAfter (const $ restore thing)
370+
where
371+
-- ignore the exception from after, see bracket for explanation
372+
cAfter () (C.ExitCaseException se) | Just ex <- fromException se =
373+
ignoreExceptions $ after ex
374+
cAfter () _ = pure ()
376375

377376
-- | Async safe version of 'E.bracket'
378377
--
@@ -392,31 +391,25 @@ bracket_ before after thing = bracket before (const after) (const thing)
392391
-- @since 0.1.0.0
393392
finally :: C.MonadMask m => m a -> m b -> m a
394393
finally thing after = C.uninterruptibleMask $ \restore -> do
395-
res1 <- C.try $ restore thing
396-
case res1 of
397-
Left (e1 :: SomeException) -> do
398-
-- see bracket for explanation
399-
_ :: Either SomeException b <- C.try after
400-
C.throwM e1
401-
Right x -> do
402-
_ <- after
403-
return x
394+
fmap fst $ C.generalBracket (pure ()) cAfter (const $ restore thing)
395+
where
396+
-- ignore the exception from after, see bracket for explanation
397+
cAfter () (C.ExitCaseException se) =
398+
ignoreExceptions after
399+
cAfter () _ = void after
404400

405401
-- | Async safe version of 'E.bracketOnError'
406402
--
407403
-- @since 0.1.0.0
408404
bracketOnError :: forall m a b c. C.MonadMask m
409405
=> m a -> (a -> m b) -> (a -> m c) -> m c
410-
bracketOnError before after thing = C.mask $ \restore -> do
411-
x <- before
412-
res1 <- C.try $ restore (thing x)
413-
case res1 of
414-
Left (e1 :: SomeException) -> do
415-
-- ignore the exception, see bracket for explanation
416-
_ :: Either SomeException b <-
417-
C.try $ C.uninterruptibleMask_ $ after x
418-
C.throwM e1
419-
Right y -> return y
406+
bracketOnError before after thing = fmap fst $ C.generalBracket before cAfter thing
407+
where
408+
-- ignore the exception from after, see bracket for explanation
409+
cAfter x (C.ExitCaseException se) =
410+
C.uninterruptibleMask_ $ ignoreExceptions $ after x
411+
cAfter x _ = pure ()
412+
420413

421414
-- | A variant of 'bracketOnError' where the return value from the first
422415
-- computation is not required.
@@ -431,22 +424,21 @@ bracketOnError_ before after thing = bracketOnError before (const after) (const
431424
-- @since 0.1.7.0
432425
bracketWithError :: forall m a b c. C.MonadMask m
433426
=> m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
434-
bracketWithError before after thing = C.mask $ \restore -> do
435-
x <- before
436-
res1 <- C.try $ restore (thing x)
437-
case res1 of
438-
Left (e1 :: SomeException) -> do
439-
-- explicitly ignore exceptions from after. We know that
440-
-- no async exceptions were thrown there, so therefore
441-
-- the stronger exception must come from thing
442-
--
443-
-- https://github.com/fpco/safe-exceptions/issues/2
444-
_ :: Either SomeException b <-
445-
C.try $ C.uninterruptibleMask_ $ after (Just e1) x
446-
C.throwM e1
447-
Right y -> do
448-
_ <- C.uninterruptibleMask_ $ after Nothing x
449-
return y
427+
bracketWithError before after thing = fmap fst $ C.generalBracket before cAfter thing
428+
where
429+
cAfter x (C.ExitCaseException se) =
430+
C.uninterruptibleMask_ $ ignoreExceptions $ after (Just se) x
431+
cAfter x _ =
432+
void $ C.uninterruptibleMask_ $ after Nothing x
433+
434+
-- | Internal function that swallows all exceptions, used in some bracket-like
435+
-- combinators. When it's run inside of uninterruptibleMask, we know that
436+
-- no async exceptions can be thrown from thing, so the other exception from
437+
-- the combinator will not be overridden.
438+
--
439+
-- https://github.com/fpco/safe-exceptions/issues/2
440+
ignoreExceptions :: C.MonadMask m => m a -> m ()
441+
ignoreExceptions thing = void thing `C.catch` (\(_ :: SomeException) -> pure ())
450442

451443
-- | Wrap up an asynchronous exception to be treated as a synchronous
452444
-- exception

stack.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
resolver: lts-10.5
2-
#extra-deps:
3-
#- exceptions-0.9.0
2+
extra-deps:
3+
- exceptions-0.10.0

test/Control/Exception/SafeSpec.hs

+57
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import Control.Exception (ArithException (..), AsyncException (..), BlockedIndef
88
import qualified Control.Exception as E
99
import Control.Exception.Safe
1010
import Control.Monad (forever)
11+
import Control.Monad.Trans.Class (lift)
12+
import Control.Monad.Trans.Except (runExceptT, throwE)
13+
import Data.IORef (modifyIORef, newIORef, readIORef)
1114
import Data.Typeable (Typeable)
1215
import Data.Void (Void, absurd)
1316
import System.IO.Unsafe (unsafePerformIO)
@@ -50,6 +53,13 @@ exceptions =
5053
withAll :: (SomeException -> Bool -> IO ()) -> Spec
5154
withAll f = mapM_ (\(e, b) -> it (show e) (f e b)) exceptions
5255

56+
data ResourceAction
57+
= ResourceAcquire
58+
| ResourceUse
59+
| ResourceRelease
60+
| ExceptionObserve ExceptionPred
61+
deriving (Show, Eq)
62+
5363
spec :: Spec
5464
spec = do
5565
describe "isSyncException" $ withAll
@@ -132,3 +142,50 @@ spec = do
132142
describe "throwString" $ do
133143
it "is a StringException" $
134144
throwString "foo" `catch` \(StringException _ _) -> return () :: IO ()
145+
146+
describe "bracketWithError" $ do
147+
it "should prioritize exceptions from thing" $ do
148+
actionLogRef <- newIORef []
149+
eiResult <-
150+
try $
151+
Control.Exception.Safe.bracketWithError
152+
( do
153+
modifyIORef actionLogRef (ResourceAcquire :)
154+
)
155+
( \mbEx () -> do
156+
case mbEx of
157+
Just ex | Just exPred <- fromException ex ->
158+
modifyIORef actionLogRef (ExceptionObserve exPred :)
159+
_ -> pure ()
160+
modifyIORef actionLogRef (ResourceRelease :)
161+
throw $ ExceptionPred $ Just ()
162+
)
163+
( \() -> do
164+
modifyIORef actionLogRef (ResourceUse :)
165+
throw $ ExceptionPred Nothing
166+
pure ()
167+
)
168+
eiResult `shouldBe` Left (ExceptionPred Nothing)
169+
readIORef actionLogRef
170+
`shouldReturn` [ResourceRelease, ExceptionObserve (ExceptionPred Nothing), ResourceUse, ResourceAcquire]
171+
172+
it "should lift through ExceptT" $ do
173+
actionLogRef <- newIORef []
174+
eiResult <-
175+
runExceptT $
176+
Control.Exception.Safe.bracketWithError
177+
( do
178+
lift $ modifyIORef actionLogRef (ResourceAcquire :)
179+
)
180+
( \_ () -> do
181+
lift $ modifyIORef actionLogRef (ResourceRelease :)
182+
)
183+
( \() -> do
184+
lift $ modifyIORef actionLogRef (ResourceUse :)
185+
throwE $ ExceptionPred Nothing
186+
pure ()
187+
)
188+
eiResult `shouldBe` Left (ExceptionPred Nothing)
189+
readIORef actionLogRef
190+
`shouldReturn` [ResourceRelease, ResourceUse, ResourceAcquire]
191+

0 commit comments

Comments
 (0)