Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

finally and bracket don't work with ExceptT #36

Closed
delfigamer opened this issue Jul 8, 2021 · 1 comment · Fixed by #37
Closed

finally and bracket don't work with ExceptT #36

delfigamer opened this issue Jul 8, 2021 · 1 comment · Fixed by #37

Comments

@delfigamer
Copy link
Contributor

-- | Async safe version of 'E.bracket' with access to the exception in the
-- cleanup action.
--
-- @since 0.1.7.0
bracketWithError :: forall m a b c. C.MonadMask m
=> m a -> (Maybe SomeException -> a -> m b) -> (a -> m c) -> m c
bracketWithError before after thing = C.mask $ \restore -> do
x <- before
res1 <- C.try $ restore (thing x)
case res1 of
Left (e1 :: SomeException) -> do
-- explicitly ignore exceptions from after. We know that
-- no async exceptions were thrown there, so therefore
-- the stronger exception must come from thing
--
-- https://github.com/fpco/safe-exceptions/issues/2
_ :: Either SomeException b <-
C.try $ C.uninterruptibleMask_ $ after (Just e1) x
C.throwM e1
Right y -> do
_ <- C.uninterruptibleMask_ $ after Nothing x
return y

If the underlying monad is ExceptT, and the interior action performs throwError, then the release action gets skipped.

This can be checked with this test:

    describe "bracketWithError" $ do
      it "should lift through ExceptT properly" $ do
        ref <- newIORef ("no resource" :: Text)
        _ <-
          runExceptT $
            Control.Exception.Safe.bracketWithError
              ( do
                  lift $ readIORef ref `shouldReturn` "no resource"
                  lift $ writeIORef ref "resource acquired"
              )
              ( \_ () -> do
                  lift $ readIORef ref `shouldReturn` "resource used"
                  lift $ writeIORef ref "resource released"
              )
              (\() -> do
                  lift $ readIORef ref `shouldReturn` "resource acquired"
                  lift $ writeIORef ref "resource used"
                  throwError ControlledException
              )
          :: IO (Either ControlledException Void)
        readIORef ref `shouldReturn` "resource released"

With the current version (0.1.7.1), this test fails with expected: "resource released", but got: "resource used".

Since finally and bracket are written as special cases of bracketOnError, they get affected by this bug as well.

As this library is using exceptions's monad typeclasses, I think this function should be re-written in terms of generalBracket, so that it would handle the ExceptT transformer too.

@snoyberg
Copy link
Member

snoyberg commented Jul 8, 2021

Seems like a reasonable change, interested in sending a PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants