conduit
conduit copied to clipboard
Question: how to differentiate between normal and abnormal termination?
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
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'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 [email protected] 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 https://github.com/snoyberg/conduit/issues/278#issuecomment-242898637, or mute the thread https://github.com/notifications/unsubscribe-auth/AADBB3_T3U3xQY6OPruztya5hxHzf6Zrks5qj9IvgaJpZM4JunZ1 .