-- | -- Module : Streamly.Internal.Data.IOFinalizer.Lifted -- Copyright : (c) 2020 Composewell Technologies and Contributors -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC -- -- A value associated with an IO action that is automatically called whenever -- the value is garbage collected. module Streamly.Internal.Data.IOFinalizer.Lifted ( IOFinalizer , newIOFinalizer , runIOFinalizer , clearingIOFinalizer ) where import Control.Exception (mask_) import Control.Monad (void) import Control.Monad.IO.Class (MonadIO(..)) import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef) import Streamly.Internal.Control.Concurrent (MonadRunInIO, askRunInIO, runInIO, withRunInIO) import Streamly.Internal.Data.IOFinalizer (IOFinalizer(..), runIOFinalizer) -- | Make a finalizer from a monadic action @m a@ that can run in IO monad. mkIOFinalizer :: MonadRunInIO m => m b -> m (IO ()) mkIOFinalizer f = do mrun <- askRunInIO return $ void $ do _ <- runInIO mrun f return () -- | GC hook to run an IO action stored in a finalized IORef. runFinalizerGC :: IORef (Maybe (IO ())) -> IO () runFinalizerGC ref = do res <- readIORef ref case res of Nothing -> return () Just f -> f -- | Create a finalizer that calls the supplied function automatically when the -- it is garbage collected. -- -- /The finalizer is always run using the state of the monad that is captured -- at the time of calling 'newFinalizer'./ -- -- Note: To run it on garbage collection we have no option but to use the monad -- state captured at some earlier point of time. For the case when the -- finalizer is run manually before GC we could run it with the current state -- of the monad but we want to keep both the cases consistent. -- -- /Pre-release/ newIOFinalizer :: MonadRunInIO m => m a -> m IOFinalizer newIOFinalizer finalizer = do f <- mkIOFinalizer finalizer ref <- liftIO $ newIORef $ Just f _ <- liftIO $ mkWeakIORef ref (runFinalizerGC ref) return $ IOFinalizer ref -- | Run an action clearing the finalizer atomically wrt async exceptions. The -- action is run with async exceptions masked. -- -- /Pre-release/ clearingIOFinalizer :: MonadRunInIO m => IOFinalizer -> m a -> m a clearingIOFinalizer (IOFinalizer ref) action = do withRunInIO $ \runinio -> mask_ $ do writeIORef ref Nothing runinio action