Skip to content

Commit b8f1ae8

Browse files
Move the effect based resource ops to Exception module
1 parent 21e0d7a commit b8f1ae8

File tree

4 files changed

+202
-178
lines changed

4 files changed

+202
-178
lines changed

core/src/Streamly/Internal/Control/Exception.hs

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,41 @@
1010
-- Additional "Control.Exception" utilities.
1111

1212
module 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
)
1632
where
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 #-}
3767
verifyM :: Applicative f => Bool -> f ()
3868
verifyM 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

Comments
 (0)