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