1010-- Additional "Control.Exception" utilities.
1111
1212module Streamly.Internal.Control.Exception
13- ( verify
13+ (
14+ -- * Verify
15+ verify
1416 , verifyM
17+
18+ -- * Resource Management
19+ -- | Exception safe, thread safe resource managment operations, similar to
20+ -- but more powerful than the bracket and finally operations available in
21+ -- the base package.
22+ --
23+ -- These operations support allocation and free only in the IO monad,
24+ -- therefore, they have the IO suffix.
25+ --
26+ , AllocateIO (.. )
27+ , RegisterIO (.. )
28+ , allocToRegIO
29+ , withRegisterIO
30+ , withAllocateIO
1531 )
1632where
1733
34+ import Control.Monad (void )
35+ import Control.Monad.IO.Class (MonadIO (.. ))
36+ import Control.Exception (mask_ )
37+ import Control.Monad.Catch (MonadMask )
38+ import Data.Foldable (sequenceA_ )
39+ import Data.IORef (newIORef , readIORef , atomicModifyIORef )
40+
41+ import qualified Control.Monad.Catch as MC
42+ import qualified Data.Map.Strict as Map
43+
44+ -------------------------------------------------------------------------------
45+ -- Asserts
46+ -------------------------------------------------------------------------------
47+
1848-- | Like 'assert' but is not removed by the compiler, it is always present in
1949-- production code.
2050--
@@ -36,3 +66,122 @@ verify predicate val =
3666{-# INLINE verifyM #-}
3767verifyM :: Applicative f => Bool -> f ()
3868verifyM predicate = verify predicate (pure () )
69+
70+ -------------------------------------------------------------------------------
71+ -- Resource management
72+ -------------------------------------------------------------------------------
73+
74+ -- To keep the type signatures simple and to avoid inference problems we should
75+ -- use this newtype. We cannot pass around a foralled type without wrapping
76+ -- them it in a newtype.
77+
78+ -- | @AllocateIO f@ is a newtype wrapper for an IO monad allocator function @f@.
79+ --
80+ -- The allocator function @f alloc free@ is used in bracket-style safe resource
81+ -- allocation functions, where @alloc@ is a function used to allocate a
82+ -- resource and @free@ is used to free it. The allocator returns a tuple
83+ -- @(resource, release)@ where @resource@ is the allocated resource and
84+ -- @release@ is an action that can be called later to release the resource.
85+ --
86+ newtype AllocateIO = AllocateIO
87+ (forall b c . IO b -> (b -> IO c ) -> IO (b , IO () ))
88+
89+ -- | @RegisterIO f@ is a newtype wrapper for a hook registration function @f@.
90+ --
91+ -- @f hook@ is used to register hooks to be executed at the end of
92+ -- finally style functions.
93+ --
94+ newtype RegisterIO = RegisterIO (forall c . IO c -> IO () )
95+
96+ -- | @withAllocateIO action@ runs the given @action@, providing it with a
97+ -- special function called @allocator@ as argument. An @allocator alloc
98+ -- free@ call can be used within @action@ any number of times to allocate
99+ -- resources that are automatically freed when 'withAllocateIO' ends or if an
100+ -- exception occurs at any time. @alloc@ is a function used to allocate a
101+ -- resource and @free@ is to free the allocated resource. @allocator@
102+ -- returns @(resource, release)@ -- the allocated @resource@ and a @release@
103+ -- action to release it.
104+ --
105+ -- @allocator@ allocates a resource in an exception safe manner and
106+ -- sets up its automatic release on exception or when @withAllocateIO@ ends.
107+ -- The @release@ function returned by @allocator@ can be used to free the
108+ -- resource manually at any time. @release@ is guaranteed to free the resource
109+ -- only once even if it is called concurrently or multiple times.
110+ --
111+ -- This function provides functionality similar to the @bracket@ function
112+ -- available in the base library. However, it is more powerful as any number of
113+ -- resources can be allocated at any time within the scope and can be released
114+ -- at any time.
115+ --
116+ -- Exception safe, thread safe.
117+ {-# INLINE withAllocateIO #-}
118+ withAllocateIO :: (MonadIO m , MonadMask m ) => (AllocateIO -> m a ) -> m a
119+ withAllocateIO action = do
120+ ref <- liftIO $ newIORef (0 :: Int , Map.empty )
121+ action (AllocateIO (bracket ref)) `MC.finally` aft ref
122+
123+ where
124+
125+ -- This is called from a the same thread as the main action, therefore, we
126+ -- do not need to worry about concurrent execution.
127+ aft ref = liftIO $ do
128+ xs <- readIORef ref
129+ sequence_ (snd xs)
130+
131+ bracket ref alloc free = do
132+ (r, index) <- liftIO $ mask_ $ do
133+ r <- alloc
134+ idx <- atomicModifyIORef ref (\ (i, mp) ->
135+ ((i + 1 , Map. insert i (void $ free r) mp), i))
136+ return (r, idx)
137+
138+ let modify (i, mp) =
139+ let res = Map. lookup index mp
140+ in ((i, Map. delete index mp), res)
141+ free1 = do
142+ res <- atomicModifyIORef ref modify
143+ sequence_ res
144+ return (r, free1)
145+
146+ -- | Convert an @allocate@ function to a hook registration function.
147+ --
148+ allocToRegIO :: AllocateIO -> RegisterIO
149+ allocToRegIO (AllocateIO f) = RegisterIO (void . g)
150+
151+ where
152+
153+ g x = f (return () ) (\ () -> x)
154+
155+ -- | @withRegisterIO action@ runs the given @action@, providing it with a
156+ -- special function called @register@ as argument. A @register hook@ call can
157+ -- be used within @action@ any number of times to register hooks that would
158+ -- run automatically when 'withRegisterIO' ends or if an exception occurs at
159+ -- any time.
160+ --
161+ -- This function provides functionality similar to the @finally@ function
162+ -- available in the @base@ package. However, it is more powerful as any number
163+ -- of hooks can be registered at any time within the scope of @withRegisterIO@.
164+ --
165+ -- Exception safe, thread safe.
166+ {-# INLINE withRegisterIO #-}
167+ withRegisterIO :: forall m a . (MonadIO m , MonadMask m ) =>
168+ (RegisterIO -> m a ) -> m a
169+ {-
170+ withRegisterIO action = do
171+ let f bracket = do
172+ let reg hook = void $ bracket (return ()) (\() -> void hook)
173+ action reg
174+ in withAllocateIO f
175+ -}
176+ withRegisterIO action = do
177+ ref <- liftIO $ newIORef []
178+ action (RegisterIO (register ref)) `MC.finally` aft ref
179+
180+ where
181+
182+ aft ref = liftIO $ do
183+ xs <- readIORef ref
184+ sequenceA_ xs
185+
186+ register ref f =
187+ atomicModifyIORef ref (\ xs -> (void f : xs, () ))
0 commit comments