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

Question: how to differentiate between normal and abnormal termination? #278

Open
edsko opened this issue Aug 27, 2016 · 2 comments

Comments

@edsko
Copy link

commented Aug 27, 2016

The main primitive for exception handling in conduit (I think) is

bracketP :: MonadResource m  
         => IO a    
         -> (a -> IO ())    
         -> (a -> ConduitM i o m r) 
         -> ConduitM i o m r     

but this function does not allow us to differentiate between normal and abnormal termination, which sometimes affects the way we want to cleanup a resource. For example, in a conduit that wraps a database transaction, we might want to commit on regular termination but abort when an exception arises. Ideally we'd have something like

bracketPE :: MonadResource m     
          => IO a   
          -> (Maybe Exception -> a -> IO ())    
          -> (a -> ConduitM i o m r)    
          -> ConduitM i o m r    

or something along those lines, so that the cleanup handler can distinguish between these two cases. However, I don't know how to implement this, especially since MonadResource does not require MonadMask. I ended up implementing

type IsReleased = Bool

bracketPE :: forall i o m r a. MonadResource m
          => IO a         -- ^ Allocation
          -> (a -> IO ()) -- ^ Finalize (cleanup on normal termination)
          -> (a -> IO ()) -- ^ Abort (cleanup on abnormal termination)
          -> (a -> ConduitM i o m r) -- ^ Body
          -> ConduitM i o m r
bracketPE alloc finalize abort k =
    bracketP acq rel body
  where
    acq :: IO (a, IORef IsReleased)
    acq = (,) <$> alloc <*> newIORef False

    rel :: (a, IORef IsReleased) -> IO ()
    rel (a, ref) = do
        isReleased <- readIORef ref
        unless isReleased $ abort a

    body :: (a, IORef IsReleased) -> ConduitM i o m r
    body (a, ref) = do
        r <- k a
        liftIO $ uninterruptibleMask_ $ finalize a >> writeIORef ref True
        return r

which is almost the same (it doesn't provide the actual exception, but it's good enough for my current purposes). But I don't know if there is a more idiomatic way of doing this (not tto mention that it's hard to get this kind of code right, so I'm not 100% convinced there's no flaw in my above implementation).

For completeness, here's how I would write it if I had a MonadMask instance:

bracketE :: MonadMask m
         => m a         -- ^ Allocation
         -> (a -> m ()) -- ^ Finanlize (cleanup on normal termination)
         -> (a -> m ()) -- ^ Abort (cleanup on abnormal termination)
         -> (a -> m r)  -- ^ Body
         -> m r
bracketE alloc finalize abort k =
  mask $ \restore -> do
    a <- alloc
    r <- restore (k a) `onException` abort a
    _ <- finalize a
    return r
@edsko

This comment has been minimized.

Copy link
Author

commented Aug 27, 2016

Actually, this version is somewhat more general, and has the benefit of making it clear in the types what's going on:

-- | Conduit equivalent of 'bracketE'
bracketPE :: forall i o m r r' a. MonadResource m
          => IO a              -- ^ Allocation
          -> (a -> r -> IO r') -- ^ Finalize (cleanup on normal termination)
          -> (a -> IO ())      -- ^ Abort (cleanup on abnormal termination)
          -> (a -> ConduitM i o m r) -- ^ Body
          -> ConduitM i o m r'
bracketPE alloc finalize abort k =
    bracketP acq rel body
  where
    acq :: IO (a, IORef IsReleased)
    acq = (,) <$> alloc <*> newIORef False

    rel :: (a, IORef IsReleased) -> IO ()
    rel (a, ref) = do
        isReleased <- readIORef ref
        unless isReleased $ abort a

    body :: (a, IORef IsReleased) -> ConduitM i o m r'
    body (a, ref) = do
        r <- k a
        liftIO $ uninterruptibleMask_ $ do
          r' <- finalize a r
          writeIORef ref True
          return r'
@snoyberg

This comment has been minimized.

Copy link
Owner

commented Aug 28, 2016

You're right that there's no function for this now. I based the API on the
Control.Exception bracket API which doesn't provide this either. I'd be
open to a PR to add a more generalized function.

On Sat, Aug 27, 2016, 8:59 AM Edsko de Vries notifications@github.com
wrote:

Actually, this version is somewhat more general, and has the benefit of
making it clear in the types what's going on:

-- | Conduit equivalent of 'bracketE'bracketPE :: forall i o m r r' a. MonadResource m

      => IO a              -- ^ Allocation


      -> (a -> r -> IO r') -- ^ Finalize (cleanup on normal termination)


      -> (a -> IO ())      -- ^ Abort (cleanup on abnormal termination)
      -> (a -> ConduitM i o m r) -- ^ Body
      -> ConduitM i o m r'

bracketPE alloc finalize abort k =
bracketP acq rel body
where
acq :: IO (a, IORef IsReleased)
acq = (,) <$> alloc <*> newIORef False

rel :: (a, IORef IsReleased) -> IO ()
rel (a, ref) = do
    isReleased <- readIORef ref
    unless isReleased $ abort a

body :: (a, IORef IsReleased) -> ConduitM i o m r'
body (a, ref) = do


    r <- k a
    liftIO $ uninterruptibleMask_ $ do
      r' <- finalize a r
      writeIORef ref True
      return r'


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#278 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADBB3_T3U3xQY6OPruztya5hxHzf6Zrks5qj9IvgaJpZM4JunZ1
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.