Gabriella439 / Haskell-Pipes-Safe-Library

Safety for the pipes ecosystem
BSD 3-Clause "New" or "Revised" License
26 stars 21 forks source link

Finalizer not called with some uses of runSafeP and nested pipes #12

Open Davorak opened 10 years ago

Davorak commented 10 years ago

I think there was a Haskell-pipes mailing list discussion that I started about this a while back. I will link it if I find it later, I searched for a few moments with out luck. I thought this should be documented for others to reference since it is often hard to search through mailing lists.

The conclusion as I remember it was the problem came down to C.bracket in runSafeT.

runSafeT :: (MonadCatch m, MonadIO m) => SafeT m r -> m r
runSafeT m = C.bracket
    (liftIO $ newIORef $! Finalizers 0 M.empty)
    (\ioref -> do
        Finalizers _ fs <- liftIO (readIORef ioref)
        mapM snd (M.toDescList fs) )
    (R.runReaderT (unSafeT m))

The problem was really about Control.Monad.Catch and last I heard Edward, Gabriel, and Snoyman where going to put their head together and come up with a better Mona.Catch.

I think Gabriel has also mentioned using an idea that is in the ballpark of "free categories" to handle this type of problem among other things.

Here is the example that fails to close the "tmp2" file.

ghci> runSafeT . runEffect $ readFile' "tmp" >-> writeFile' "tmp2"
{tmp2 open}
{tmp open}
{tmp closed}

I have put the rest of the code in the following gist: https://gist.github.com/Davorak/f75f6df327cbc4742f16

christian-marie commented 8 years ago

Did you find that link? Is this still the case? We're using this in some production code and are hoping that this abstraction is not broken.

Gabriella439 commented 8 years ago

This is the discussion: https://groups.google.com/d/topic/haskell-pipes/kYrol5F4B5M/discussion

I can summarize the discussion by saying:

The reason why is that runSafeT uses the MonadCatch interface for the monad beneath it, which does the right thing if the monad beneath it is IO, but does the wrong thing if the monad beneath it is Proxy (because we can't use the MonadCatch interface to call Pipes.Safe.bracket).