on ffca3ac help sign in
102 lines 3.5 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 Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
25import Streamly.Internal.Control.Concurrent (MonadRunInIO, askRunInIO, runInIO, withRunInIO)
27-- | An 'IOFinalizer' has an associated IO action that is automatically called
28-- whenever the finalizer is garbage collected. The action can be run and
29-- cleared prematurely.
30--
31-- You can hold a reference to the finalizer in your data structure, if the
32-- data structure gets garbage collected the finalizer will be called.
33--
34-- It is implemented using 'mkWeakIORef'.
35--
36-- /Pre-release/
37newtype IOFinalizer = IOFinalizer (IORef (Maybe (IO ())))
39-- | Make a finalizer from a monadic action @m a@ that can run in IO monad.
40mkIOFinalizer :: MonadRunInIO m => m b -> m (IO ())
41mkIOFinalizer f = do
42 mrun <- askRunInIO
43 return $
44 void $ do
45 _ <- runInIO mrun f
46 return ()
48-- | GC hook to run an IO action stored in a finalized IORef.
49runFinalizerGC :: IORef (Maybe (IO ())) -> IO ()
50runFinalizerGC ref = do
51 res <- readIORef ref
52 case res of
53 Nothing -> return ()
54 Just f -> f
56-- | Create a finalizer that calls the supplied function automatically when the
57-- it is garbage collected.
58--
59-- /The finalizer is always run using the state of the monad that is captured
60-- at the time of calling 'newFinalizer'./
61--
62-- Note: To run it on garbage collection we have no option but to use the monad
63-- state captured at some earlier point of time. For the case when the
64-- finalizer is run manually before GC we could run it with the current state
65-- of the monad but we want to keep both the cases consistent.
66--
67-- /Pre-release/
68newIOFinalizer :: MonadRunInIO m => m a -> m IOFinalizer
69newIOFinalizer finalizer = do
70 f <- mkIOFinalizer finalizer
71 ref <- liftIO $ newIORef $ Just f
72 _ <- liftIO $ mkWeakIORef ref (runFinalizerGC ref)
73 return $ IOFinalizer ref
75-- | Run the action associated with the finalizer and deactivate it so that it
76-- never runs again. Note, the finalizing action runs with async exceptions
77-- masked.
78--
79-- /Pre-release/
80runIOFinalizer :: MonadIO m => IOFinalizer -> m ()
81runIOFinalizer (IOFinalizer ref) = liftIO $ do
82 res <- readIORef ref
83 case res of
84 Nothing -> return ()
85 Just action -> do
86 -- if an async exception comes after writing 'Nothing' then the
87 -- finalizing action will never be run. We need to do this
88 -- atomically wrt async exceptions.
89 mask_ $ do
90 writeIORef ref Nothing
91 action
93-- | Run an action clearing the finalizer atomically wrt async exceptions. The
94-- action is run with async exceptions masked.
95--
96-- /Pre-release/
97clearingIOFinalizer :: MonadRunInIO m => IOFinalizer -> m a -> m a
98clearingIOFinalizer (IOFinalizer ref) action = do
99 withRunInIO $ \runinio ->
100 mask_ $ do
101 writeIORef ref Nothing
102 runinio action