@@ -11,11 +11,9 @@ module Effectful.Internal.Monad
13 -- ** Access to the internal representation
13 -- ** Access to the internal representation
16 , unsafeEff
15 , unsafeEff
17 , unsafeEff_
16 , unsafeEff_
@@ -25,7 +23,18 @@ module Effectful.Internal.Monad
26+ -- ** Unlift strategies
28− -- * Low-level helpers
33+ --- *** Low-level helpers
37+ -- * Primitive effects
30 -- ** Running
39 -- ** Running
31 , runEffect
40 , runEffect
@@ -50,12 +59,14 @@ import Control.Monad.IO.Class
50import Control.Monad.IO.Unlift
59import Control.Monad.IO.Unlift
51import Control.Monad.Trans.Control
60import Control.Monad.Trans.Control
52import Data.Unique
61import Data.Unique
53−import GHC.Magic (oneShot)
62+import GHC.Exts (oneShot)
54import System.IO.Unsafe (unsafeDupablePerformIO)
63import System.IO.Unsafe (unsafeDupablePerformIO)
55import qualified Control.Monad.Catch as E
64import qualified Control.Monad.Catch as E
57import Effectful.Internal.Effect
66import Effectful.Internal.Effect
58import Effectful.Internal.Env
67import Effectful.Internal.Env
68+import Effectful.Internal.Unlift
69+import Effectful.Internal.Utils
60type role Eff nominal representational
71type role Eff nominal representational
@@ -113,40 +124,32 @@ unsafeEff m = Eff (oneShot m)
113unsafeEff_ :: IO a -> Eff es a
124unsafeEff_ :: IO a -> Eff es a
114unsafeEff_ m = unsafeEff $ \_ -> m
125unsafeEff_ m = unsafeEff $ \_ -> m
116−-- | A function that runs 'Eff' operations in @m@ (usually a local 'Eff'
117−-- environment or 'IO').
118−type RunIn es m = forall r. Eff es r -> m r
120−-- | Lower 'Eff' operations into 'IO'.
127+-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
121−--
128+seqUnliftEff :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
122−-- /Note:/ the 'RunIn' argument has to be called from the same thread as
123−-- 'unsafeUnliftEff' ('liftBaseWith' / 'withRunInIO'). Attempting otherwise
124−-- results in a runtime error, because it cannot be done safely in general
125−-- (*). If you need to spawn threads, have a look at "Effectful.Async" or create
126−-- a new 'Eff' environment.
128−-- (*) Consider the following:
132−-- liftBaseWith $ \run -> forkIO $ do
133−-- threadDelay 1000000
139−-- If (1) runs after (2) completed, the State Char effect is no longer in scope,
140−-- but (1) assumes otherwise and things break horribly.
141−unsafeUnliftEff :: (RunIn es IO -> IO a) -> Eff es a
142−unsafeUnliftEff f = unsafeEff $ \es -> do
129+seqUnliftEff f = unsafeEff $ \es -> do
143 tid0 <- myThreadId
130 tid0 <- myThreadId
144 f $ \m -> do
131 f $ \m -> do
145 tid <- myThreadId
132 tid <- myThreadId
146− if tid == tid0
133+ if tid `eqThreadId` tid0
147 then unEff m es
134 then unEff m es
148− else error $ "Running Eff operations in a different thread is "
135+ else error
149− ++ "currently not possible with the unlifting function"
136+ $ "If you want to use the unlifting function to run Eff operations "
137+ ++ "in multiple threads, have a look at UnliftStrategy (ConcUnlift)."
139+-- | Lower 'Eff' operations into 'IO' ('ConcUnlift').
143+ -> ((forall r. Eff es r -> IO r) -> IO a)
145+concUnliftEff Ephemeral (Limited uses) f = unsafeEff $ \es -> do
146+ ephemeralConcUnliftIO uses f es unEff
147+concUnliftEff Ephemeral Unlimited f = unsafeEff $ \es -> do
148+ ephemeralConcUnliftIO maxBound f es unEff
149+concUnliftEff Persistent (Limited threads) f = unsafeEff $ \es -> do
150+ persistentConcUnliftIO False threads f es unEff
151+concUnliftEff Persistent Unlimited f = unsafeEff $ \es -> do
152+ persistentConcUnliftIO True maxBound f es unEff
151----------------------------------------
154----------------------------------------
@@ -246,14 +249,14 @@ tryFailIO tag m =
247-- | Run arbitrary 'IO' operations via 'MonadIO', 'MonadBaseControl' 'IO' or
250-- | Run arbitrary 'IO' operations via 'MonadIO', 'MonadBaseControl' 'IO' or
248-- 'MonadUnliftIO'.
251-- 'MonadUnliftIO'.
249−data IOE :: Effect where
252+newtype IOE :: Effect where
250− IOE :: IOE m r
253+ IOE :: UnliftStrategy -> IOE m r
252-- | Run an 'Eff' operation with side effects.
255-- | Run an 'Eff' operation with side effects.
254-- For the pure version see 'runEff'.
257-- For the pure version see 'runEff'.
255runIOE :: Eff '[IOE] a -> IO a
258runIOE :: Eff '[IOE] a -> IO a
256−runIOE m = unEff (evalEffect (IdE IOE) m) =<< emptyEnv
259+runIOE m = unEff (evalEffect (IdE (IOE SeqUnlift)) m) =<< emptyEnv
258instance IOE :> es => MonadIO (Eff es) where
261instance IOE :> es => MonadIO (Eff es) where
259 liftIO = unsafeEff_
262 liftIO = unsafeEff_
@@ -263,11 +266,82 @@ instance IOE :> es => MonadBase IO (Eff es) where
264instance IOE :> es => MonadBaseControl IO (Eff es) where
267instance IOE :> es => MonadBaseControl IO (Eff es) where
265 type StM (Eff es) a = a
268 type StM (Eff es) a = a
266− liftBaseWith = unsafeUnliftEff
269+ liftBaseWith = unliftEff
267 restoreM = pure
270 restoreM = pure
269instance IOE :> es => MonadUnliftIO (Eff es) where
272instance IOE :> es => MonadUnliftIO (Eff es) where
270− withRunInIO = unsafeUnliftEff
273+ withRunInIO = unliftEff
275+----------------------------------------
278+-- | The strategy to use when unlifting 'Eff' operations via 'liftBaseWith',
279+-- 'withRunInIO' or the 'Effectful.Interpreter.localUnlift' family.
282+ -- ^ The fastest strategy and a default setting for 'IOE'. An attempt to call
283+ -- the unlifting function in threads distinct from its creator will result in
285+ | ConcUnlift Persistence Limit
286+ -- ^ A strategy that makes it possible for the unlifting function to be called
287+ -- in threads distinct from its creator. See 'Persistence' and 'Limit'
288+ -- settings for more information.
289+ deriving (Eq, Ord, Show)
291+-- | Persistence setting for the 'ConcUnlift' strategy.
293+-- Different functions require different persistence strategies. Examples:
295+-- - Lifting 'pooledMapConcurrentlyN' from the @unliftio@ library requires the
296+-- 'Ephemeral' strategy as we don't want jobs to share environment changes
297+-- made by previous jobs run in the same worker thread.
299+-- - Lifting 'Control.Concurrent.forkIOWithUnmask' requires the 'Persistent'
300+-- strategy, otherwise the unmasking function would start with a fresh
301+-- environment each time it's called.
304+ -- ^ Don't persist the environment between calls to the unlifting function in
305+ -- threads distinct from its creator.
307+ -- ^ Persist the environment between calls to the unlifting function within a
308+ -- particular thread.
309+ deriving (Eq, Ord, Show)
311+-- | Limit setting for the 'ConcUnlift' strategy.
314+ -- ^ Behavior dependent on the 'Persistence' setting.
316+ -- For 'Ephemeral', it limits the amount of uses of the unlifting function in
317+ -- threads distinct from its creator to @N@. The unlifting function will
318+ -- create @N@ copies of the environment when called @N@ times and @K+1@ copies
319+ -- when called @K < N@ times.
321+ -- For 'Persistent', it limits the amount of threads, distinct from the
322+ -- creator of the unlifting function, it can be called in to @N@. The amount
323+ -- of calls to the unlifting function within a particular threads is
324+ -- unlimited. The unlifting function will create @N@ copies of the environment
325+ -- when called in @N@ threads and @K+1@ copies when called in @K < N@ threads.
327+ -- ^ Unlimited use of the unlifting function.
328+ deriving (Eq, Ord, Show)
330+-- | Get the current 'UnliftStrategy'.
331+unliftStrategy :: IOE :> es => Eff es UnliftStrategy
332+unliftStrategy = readerEffectM $ \(IdE (IOE unlift)) -> pure unlift
334+-- | Locally override the 'UnliftStrategy' with a given value.
336+-- /Note:/ the strategy is always reset to 'SeqUnlift' for new threads.
337+withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a
338+withUnliftStrategy unlift = localEffect $ \_ -> IdE (IOE unlift)
340+-- | Helper for 'MonadBaseControl' and 'MonadUnliftIO' instances.
341+unliftEff :: IOE :> es => ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
342+unliftEff f = unliftStrategy >>= \case
343+ SeqUnlift -> seqUnliftEff f
344+ ConcUnlift p b -> withUnliftStrategy SeqUnlift $ concUnliftEff p b f
272----------------------------------------
346----------------------------------------
273-- Helpers
347-- Helpers
@@ -317,8 +391,8 @@ readerEffectM f = unsafeEff $ \es -> getEnv es >>= \e -> unEff (f e) es
317{-# INLINE readerEffectM #-}
391{-# INLINE readerEffectM #-}
319stateEffectM :: e :> es => (i e -> Eff es (a, i e)) -> Eff es a
393stateEffectM :: e :> es => (i e -> Eff es (a, i e)) -> Eff es a
320−stateEffectM f = unsafeEff $ \es -> do
394+stateEffectM f = unsafeEff $ \es -> mask $ \release -> do
321− (a, e) <- (\e -> unEff (f e) es) =<< getEnv es
395+ (a, e) <- (\e -> release $ unEff (f e) es) =<< getEnv es
322 unsafePutEnv e es
396 unsafePutEnv e es
324{-# INLINE stateEffectM #-}
398{-# INLINE stateEffectM #-}