on a775bec help sign in
103 lines 3.6 kB history raw
1-- |
2-- Module : Streamly.Internal.Data.IOFinalizer
3-- Copyright : (c) 2020 Composewell Technologies and Contributors
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- A value associated with an IO action that is automatically called whenever
10-- the value is garbage collected.
12module Streamly.Internal.Data.IOFinalizer
13 (
14 IOFinalizer
15 , newIOFinalizer
16 , runIOFinalizer
17 , clearingIOFinalizer
18 )
19where
21import Control.Exception (mask_)
22import Control.Monad (void)
23import Control.Monad.IO.Class (MonadIO(..))
24import Control.Monad.Trans.Control (MonadBaseControl, control)
25import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
26import Streamly.Internal.Control.Concurrent (captureMonadState, runInIO)
28-- | An 'IOFinalizer' has an associated IO action that is automatically called
29-- whenever the finalizer is garbage collected. The action can be run and
30-- cleared prematurely.
31--
32-- You can hold a reference to the finalizer in your data structure, if the
33-- data structure gets garbage collected the finalizer will be called.
34--
35-- It is implemented using 'mkWeakIORef'.
36--
37-- /Pre-release/
38newtype IOFinalizer = IOFinalizer (IORef (Maybe (IO ())))
40-- | Make a finalizer from a monadic action @m a@ that can run in IO monad.
41mkIOFinalizer :: MonadBaseControl IO m => m b -> m (IO ())
42mkIOFinalizer f = do
43 mrun <- captureMonadState
44 return $
45 void $ do
46 _ <- runInIO mrun f
47 return ()
49-- | GC hook to run an IO action stored in a finalized IORef.
50runFinalizerGC :: IORef (Maybe (IO ())) -> IO ()
51runFinalizerGC ref = do
52 res <- readIORef ref
53 case res of
54 Nothing -> return ()
55 Just f -> f
57-- | Create a finalizer that calls the supplied function automatically when the
58-- it is garbage collected.
59--
60-- /The finalizer is always run using the state of the monad that is captured
61-- at the time of calling 'newFinalizer'./
62--
63-- Note: To run it on garbage collection we have no option but to use the monad
64-- state captured at some earlier point of time. For the case when the
65-- finalizer is run manually before GC we could run it with the current state
66-- of the monad but we want to keep both the cases consistent.
67--
68-- /Pre-release/
69newIOFinalizer :: (MonadIO m, MonadBaseControl IO m) => m a -> m IOFinalizer
70newIOFinalizer finalizer = do
71 f <- mkIOFinalizer finalizer
72 ref <- liftIO $ newIORef $ Just f
73 _ <- liftIO $ mkWeakIORef ref (runFinalizerGC ref)
74 return $ IOFinalizer ref
76-- | Run the action associated with the finalizer and deactivate it so that it
77-- never runs again. Note, the finalizing action runs with async exceptions
78-- masked.
79--
80-- /Pre-release/
81runIOFinalizer :: MonadIO m => IOFinalizer -> m ()
82runIOFinalizer (IOFinalizer ref) = liftIO $ do
83 res <- readIORef ref
84 case res of
85 Nothing -> return ()
86 Just action -> do
87 -- if an async exception comes after writing 'Nothing' then the
88 -- finalizing action will never be run. We need to do this
89 -- atomically wrt async exceptions.
90 mask_ $ do
91 writeIORef ref Nothing
92 action
94-- | Run an action clearing the finalizer atomically wrt async exceptions. The
95-- action is run with async exceptions masked.
96--
97-- /Pre-release/
98clearingIOFinalizer :: MonadBaseControl IO m => IOFinalizer -> m a -> m a
99clearingIOFinalizer (IOFinalizer ref) action = do
100 control $ \runinio ->
101 mask_ $ do
102 writeIORef ref Nothing
103 runinio action