mirror of
https://github.com/haskell-effectful/effectful.git
synced 2024-11-27 11:44:45 +03:00
Add HasCallStack to (un)lifting functions
Now if an unlifting function fails due to insufficient limit, you'll get a nice call stack that allows to track the origin of the problem easily.
This commit is contained in:
parent
69350a46e4
commit
f9bc460236
@ -142,27 +142,31 @@ reinterpretM runHandlerEs handler m = unsafeEff $ \es -> do
|
|||||||
-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
|
-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
|
||||||
-- general version see 'localUnlift'.
|
-- general version see 'localUnlift'.
|
||||||
localSeqUnlift
|
localSeqUnlift
|
||||||
:: LocalEnv localEs
|
:: HasCallStack
|
||||||
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
|
-> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
|
||||||
-- ^ Continuation with the unlifting function in scope.
|
-- ^ Continuation with the unlifting function in scope.
|
||||||
-> Eff es a
|
-> Eff es a
|
||||||
localSeqUnlift env = localUnlift env SeqUnlift
|
localSeqUnlift (LocalEnv les) k = unsafeEff $ \es -> do
|
||||||
|
unsafeSeqUnliftEff les $ \unlift -> do
|
||||||
|
(`unEff` es) $ k $ unsafeEff_ . unlift
|
||||||
|
|
||||||
-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
|
-- | Create a local unlifting function with the 'SeqUnlift' strategy. For the
|
||||||
-- general version see 'localUnliftIO'.
|
-- general version see 'localUnliftIO'.
|
||||||
localSeqUnliftIO
|
localSeqUnliftIO
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> LocalEnv localEs
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> ((forall r. Eff localEs r -> IO r) -> IO a)
|
-> ((forall r. Eff localEs r -> IO r) -> IO a)
|
||||||
-- ^ Continuation with the unlifting function in scope.
|
-- ^ Continuation with the unlifting function in scope.
|
||||||
-> Eff es a
|
-> Eff es a
|
||||||
localSeqUnliftIO env = localUnliftIO env SeqUnlift
|
localSeqUnliftIO (LocalEnv les) k = liftIO $ unsafeSeqUnliftEff les k
|
||||||
|
|
||||||
-- | Create a local unlifting function with the given strategy.
|
-- | Create a local unlifting function with the given strategy.
|
||||||
localUnlift
|
localUnlift
|
||||||
:: LocalEnv localEs
|
:: HasCallStack
|
||||||
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> UnliftStrategy
|
-> UnliftStrategy
|
||||||
-> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
|
-> ((forall r. Eff localEs r -> Eff es r) -> Eff es a)
|
||||||
@ -178,7 +182,7 @@ localUnlift (LocalEnv les) strategy k = case strategy of
|
|||||||
|
|
||||||
-- | Create a local unlifting function with the given strategy.
|
-- | Create a local unlifting function with the given strategy.
|
||||||
localUnliftIO
|
localUnliftIO
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> LocalEnv localEs
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> UnliftStrategy
|
-> UnliftStrategy
|
||||||
@ -200,13 +204,14 @@ localUnliftIO (LocalEnv les) strategy k = case strategy of
|
|||||||
-- /Note:/ the operation must not run its argument in a separate thread,
|
-- /Note:/ the operation must not run its argument in a separate thread,
|
||||||
-- attempting to do so will result in a runtime error.
|
-- attempting to do so will result in a runtime error.
|
||||||
withLiftMap
|
withLiftMap
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> ((forall a b localEs. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
=> ((forall a b localEs. (Eff es a -> Eff es b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
||||||
-- ^ Continuation with the lifting function in scope.
|
-- ^ Continuation with the lifting function in scope.
|
||||||
-> Eff es r
|
-> Eff es r
|
||||||
withLiftMap k = unsafeWithLiftMapIO $ \liftMap -> do
|
withLiftMap k = unsafeEff $ \es -> do
|
||||||
unsafeEff $ \es -> (`unEff` es) $ k $ \f -> do
|
(`unEff` es) $ k $ \mapIO m -> unsafeEff $ \localEs -> do
|
||||||
liftMap $ (`unEff` es) . f . liftIO
|
unsafeSeqUnliftEff localEs $ \unlift -> do
|
||||||
|
(`unEff` es) . mapIO . liftIO $ unlift m
|
||||||
|
|
||||||
-- | Utility for lifting 'IO' operations of type
|
-- | Utility for lifting 'IO' operations of type
|
||||||
--
|
--
|
||||||
@ -234,11 +239,12 @@ withLiftMap k = unsafeWithLiftMapIO $ \liftMap -> do
|
|||||||
-- forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
|
-- forkIOWithUnmask $ \unmask -> unlift $ m $ liftMap unmask
|
||||||
-- :}
|
-- :}
|
||||||
withLiftMapIO
|
withLiftMapIO
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
=> ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
||||||
-- ^ Continuation with the lifting function in scope.
|
-- ^ Continuation with the lifting function in scope.
|
||||||
-> Eff es r
|
-> Eff es r
|
||||||
withLiftMapIO = unsafeWithLiftMapIO
|
withLiftMapIO k = k $ \mapIO m -> unsafeEff $ \es -> do
|
||||||
|
unsafeSeqUnliftEff es $ \unlift -> mapIO $ unlift m
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Bidirectional lifts
|
-- Bidirectional lifts
|
||||||
@ -251,7 +257,7 @@ withLiftMapIO = unsafeWithLiftMapIO
|
|||||||
-- /Note:/ depending on the operation you're lifting 'localUnlift' along with
|
-- /Note:/ depending on the operation you're lifting 'localUnlift' along with
|
||||||
-- 'withLiftMap' might be enough and is more efficient.
|
-- 'withLiftMap' might be enough and is more efficient.
|
||||||
localLiftUnlift
|
localLiftUnlift
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> LocalEnv localEs
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> UnliftStrategy
|
-> UnliftStrategy
|
||||||
@ -277,7 +283,7 @@ localLiftUnlift (LocalEnv les) strategy k = case strategy of
|
|||||||
-- /Note:/ depending on the operation you're lifting 'localUnliftIO' along with
|
-- /Note:/ depending on the operation you're lifting 'localUnliftIO' along with
|
||||||
-- 'withLiftMapIO' might be enough and is more efficient.
|
-- 'withLiftMapIO' might be enough and is more efficient.
|
||||||
localLiftUnliftIO
|
localLiftUnliftIO
|
||||||
:: IOE :> es
|
:: (HasCallStack, IOE :> es)
|
||||||
=> LocalEnv localEs
|
=> LocalEnv localEs
|
||||||
-- ^ Local environment.
|
-- ^ Local environment.
|
||||||
-> UnliftStrategy
|
-> UnliftStrategy
|
||||||
|
@ -58,17 +58,18 @@ module Effectful.Internal.Monad
|
|||||||
import Control.Applicative (liftA2)
|
import Control.Applicative (liftA2)
|
||||||
import Control.Concurrent (myThreadId)
|
import Control.Concurrent (myThreadId)
|
||||||
import Control.Exception
|
import Control.Exception
|
||||||
import Control.Monad.Base
|
|
||||||
import Control.Monad.Fix
|
import Control.Monad.Fix
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.IO.Unlift
|
|
||||||
import Control.Monad.Primitive
|
import Control.Monad.Primitive
|
||||||
import Control.Monad.Trans.Control
|
|
||||||
import Data.Unique
|
import Data.Unique
|
||||||
import GHC.Exts (oneShot)
|
import GHC.Exts (oneShot)
|
||||||
import GHC.IO (IO(..))
|
import GHC.IO (IO(..))
|
||||||
|
import GHC.Stack (HasCallStack)
|
||||||
import System.IO.Unsafe (unsafeDupablePerformIO)
|
import System.IO.Unsafe (unsafeDupablePerformIO)
|
||||||
|
import qualified Control.Monad.Base as M
|
||||||
import qualified Control.Monad.Catch as E
|
import qualified Control.Monad.Catch as E
|
||||||
|
import qualified Control.Monad.IO.Unlift as M
|
||||||
|
import qualified Control.Monad.Trans.Control as M
|
||||||
|
|
||||||
import Effectful.Internal.Effect
|
import Effectful.Internal.Effect
|
||||||
import Effectful.Internal.Env
|
import Effectful.Internal.Env
|
||||||
@ -143,13 +144,18 @@ unsafeEff_ m = unsafeEff $ \_ -> m
|
|||||||
-- /Note:/ the 'IO' operation must not run its argument in a separate thread,
|
-- /Note:/ the 'IO' operation must not run its argument in a separate thread,
|
||||||
-- attempting to do so will result in a runtime error.
|
-- attempting to do so will result in a runtime error.
|
||||||
unsafeWithLiftMapIO
|
unsafeWithLiftMapIO
|
||||||
:: ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
:: HasCallStack
|
||||||
|
=> ((forall a b localEs. (IO a -> IO b) -> Eff localEs a -> Eff localEs b) -> Eff es r)
|
||||||
-> Eff es r
|
-> Eff es r
|
||||||
unsafeWithLiftMapIO k = k $ \mapIO m -> unsafeEff $ \es -> do
|
unsafeWithLiftMapIO k = k $ \mapIO m -> unsafeEff $ \es -> do
|
||||||
unsafeSeqUnliftEff es $ \unlift -> mapIO $ unlift m
|
unsafeSeqUnliftEff es $ \unlift -> mapIO $ unlift m
|
||||||
|
|
||||||
-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
|
-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
|
||||||
unsafeSeqUnliftEff :: Env es -> ((forall r. Eff es r -> IO r) -> IO a) -> IO a
|
unsafeSeqUnliftEff
|
||||||
|
:: HasCallStack
|
||||||
|
=> Env es
|
||||||
|
-> ((forall r. Eff es r -> IO r) -> IO a)
|
||||||
|
-> IO a
|
||||||
unsafeSeqUnliftEff es k = do
|
unsafeSeqUnliftEff es k = do
|
||||||
tid0 <- myThreadId
|
tid0 <- myThreadId
|
||||||
k $ \m -> do
|
k $ \m -> do
|
||||||
@ -162,7 +168,8 @@ unsafeSeqUnliftEff es k = do
|
|||||||
|
|
||||||
-- | Lower 'Eff' operations into 'IO' ('ConcUnlift').
|
-- | Lower 'Eff' operations into 'IO' ('ConcUnlift').
|
||||||
unsafeConcUnliftEff
|
unsafeConcUnliftEff
|
||||||
:: Env es
|
:: HasCallStack
|
||||||
|
=> Env es
|
||||||
-> Persistence
|
-> Persistence
|
||||||
-> Limit
|
-> Limit
|
||||||
-> ((forall r. Eff es r -> IO r) -> IO a)
|
-> ((forall r. Eff es r -> IO r) -> IO a)
|
||||||
@ -283,20 +290,40 @@ runEff m = unEff (evalEffect (IdE (IOE SeqUnlift)) m) =<< emptyEnv
|
|||||||
instance IOE :> es => MonadIO (Eff es) where
|
instance IOE :> es => MonadIO (Eff es) where
|
||||||
liftIO = unsafeEff_
|
liftIO = unsafeEff_
|
||||||
|
|
||||||
instance IOE :> es => MonadUnliftIO (Eff es) where
|
instance IOE :> es => M.MonadUnliftIO (Eff es) where
|
||||||
withRunInIO = unliftEff
|
withRunInIO = withRunInIO
|
||||||
|
where
|
||||||
|
-- A trick to show name of the function in the call stack.
|
||||||
|
withRunInIO
|
||||||
|
:: (HasCallStack, IOE :> es)
|
||||||
|
=> ((forall r. Eff es r -> IO r) -> IO a)
|
||||||
|
-> Eff es a
|
||||||
|
withRunInIO f = unliftStrategy >>= \case
|
||||||
|
SeqUnlift -> unsafeEff $ \es -> unsafeSeqUnliftEff es f
|
||||||
|
ConcUnlift p b -> withUnliftStrategy SeqUnlift $ do
|
||||||
|
unsafeEff $ \es -> unsafeConcUnliftEff es p b f
|
||||||
|
|
||||||
-- | Instance included for compatibility with existing code, usage of 'liftIO'
|
-- | Instance included for compatibility with existing code, usage of 'liftIO'
|
||||||
-- is preferrable.
|
-- is preferrable.
|
||||||
instance IOE :> es => MonadBase IO (Eff es) where
|
instance IOE :> es => M.MonadBase IO (Eff es) where
|
||||||
liftBase = unsafeEff_
|
liftBase = unsafeEff_
|
||||||
|
|
||||||
-- | Instance included for compatibility with existing code, usage of
|
-- | Instance included for compatibility with existing code, usage of
|
||||||
-- 'withRunInIO' is preferrable.
|
-- 'withRunInIO' is preferrable.
|
||||||
instance IOE :> es => MonadBaseControl IO (Eff es) where
|
instance IOE :> es => M.MonadBaseControl IO (Eff es) where
|
||||||
type StM (Eff es) a = a
|
type StM (Eff es) a = a
|
||||||
liftBaseWith = unliftEff
|
|
||||||
restoreM = pure
|
restoreM = pure
|
||||||
|
liftBaseWith = liftBaseWith
|
||||||
|
where
|
||||||
|
-- A trick to show name of the function in the call stack.
|
||||||
|
liftBaseWith
|
||||||
|
:: (HasCallStack, IOE :> es)
|
||||||
|
=> ((forall r. Eff es r -> IO r) -> IO a)
|
||||||
|
-> Eff es a
|
||||||
|
liftBaseWith f = unliftStrategy >>= \case
|
||||||
|
SeqUnlift -> unsafeEff $ \es -> unsafeSeqUnliftEff es f
|
||||||
|
ConcUnlift p b -> withUnliftStrategy SeqUnlift $ do
|
||||||
|
unsafeEff $ \es -> unsafeConcUnliftEff es p b f
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Primitive
|
-- Primitive
|
||||||
@ -378,13 +405,6 @@ unliftStrategy = readerEffectM $ \(IdE (IOE unlift)) -> pure unlift
|
|||||||
withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a
|
withUnliftStrategy :: IOE :> es => UnliftStrategy -> Eff es a -> Eff es a
|
||||||
withUnliftStrategy unlift = localEffect $ \_ -> IdE (IOE unlift)
|
withUnliftStrategy unlift = localEffect $ \_ -> IdE (IOE unlift)
|
||||||
|
|
||||||
-- | Helper for 'MonadBaseControl' and 'MonadUnliftIO' instances.
|
|
||||||
unliftEff :: IOE :> es => ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
|
|
||||||
unliftEff f = unliftStrategy >>= \case
|
|
||||||
SeqUnlift -> unsafeEff $ \es -> unsafeSeqUnliftEff es f
|
|
||||||
ConcUnlift p b -> withUnliftStrategy SeqUnlift $ do
|
|
||||||
unsafeEff $ \es -> unsafeConcUnliftEff es p b f
|
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- Helpers
|
-- Helpers
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import Control.Monad
|
|||||||
import GHC.Conc.Sync (ThreadId(..))
|
import GHC.Conc.Sync (ThreadId(..))
|
||||||
import GHC.Exts (mkWeak#, mkWeakNoFinalizer#)
|
import GHC.Exts (mkWeak#, mkWeakNoFinalizer#)
|
||||||
import GHC.IO (IO(..))
|
import GHC.IO (IO(..))
|
||||||
|
import GHC.Stack (HasCallStack)
|
||||||
import GHC.Weak (Weak(..))
|
import GHC.Weak (Weak(..))
|
||||||
import System.Mem.Weak (deRefWeak)
|
import System.Mem.Weak (deRefWeak)
|
||||||
import qualified Data.IntMap.Strict as IM
|
import qualified Data.IntMap.Strict as IM
|
||||||
@ -25,14 +26,15 @@ import Effectful.Internal.Utils
|
|||||||
-- | Concurrent unlift that doesn't preserve the environment between calls to
|
-- | Concurrent unlift that doesn't preserve the environment between calls to
|
||||||
-- the unlifting function in threads other than its creator.
|
-- the unlifting function in threads other than its creator.
|
||||||
unsafeEphemeralConcUnliftIO
|
unsafeEphemeralConcUnliftIO
|
||||||
:: Int
|
:: HasCallStack
|
||||||
|
=> Int
|
||||||
-> ((forall r. m r -> IO r) -> IO a)
|
-> ((forall r. m r -> IO r) -> IO a)
|
||||||
-> Env es
|
-> Env es
|
||||||
-> (forall r. m r -> Env es -> IO r)
|
-> (forall r. m r -> Env es -> IO r)
|
||||||
-> IO a
|
-> IO a
|
||||||
unsafeEphemeralConcUnliftIO uses k es0 unEff = do
|
unsafeEphemeralConcUnliftIO uses k es0 unEff = do
|
||||||
when (uses < 0) $ do
|
unless (uses > 0) $ do
|
||||||
error $ "ephemeralConcUnliftIO: invalid number of uses: " ++ show uses
|
error $ "Invalid number of uses: " ++ show uses
|
||||||
tid0 <- myThreadId
|
tid0 <- myThreadId
|
||||||
-- Create a copy of the environment as a template for the other threads to
|
-- Create a copy of the environment as a template for the other threads to
|
||||||
-- use. This can't be done from inside the callback as the environment might
|
-- use. This can't be done from inside the callback as the environment might
|
||||||
@ -57,15 +59,16 @@ unsafeEphemeralConcUnliftIO uses k es0 unEff = do
|
|||||||
-- | Concurrent unlift that preserves the environment between calls to the
|
-- | Concurrent unlift that preserves the environment between calls to the
|
||||||
-- unlifting function within a particular thread.
|
-- unlifting function within a particular thread.
|
||||||
unsafePersistentConcUnliftIO
|
unsafePersistentConcUnliftIO
|
||||||
:: Bool
|
:: HasCallStack
|
||||||
|
=> Bool
|
||||||
-> Int
|
-> Int
|
||||||
-> ((forall r. m r -> IO r) -> IO a)
|
-> ((forall r. m r -> IO r) -> IO a)
|
||||||
-> Env es
|
-> Env es
|
||||||
-> (forall r. m r -> Env es -> IO r)
|
-> (forall r. m r -> Env es -> IO r)
|
||||||
-> IO a
|
-> IO a
|
||||||
unsafePersistentConcUnliftIO cleanUp threads k es0 unEff = do
|
unsafePersistentConcUnliftIO cleanUp threads k es0 unEff = do
|
||||||
when (threads < 0) $ do
|
unless (threads > 0) $ do
|
||||||
error $ "persistentConcUnliftIO: invalid number of threads: " ++ show threads
|
error $ "Invalid number of threads: " ++ show threads
|
||||||
tid0 <- myThreadId
|
tid0 <- myThreadId
|
||||||
-- Create a copy of the environment as a template for the other threads to
|
-- Create a copy of the environment as a template for the other threads to
|
||||||
-- use. This can't be done from inside the callback as the environment might
|
-- use. This can't be done from inside the callback as the environment might
|
||||||
|
Loading…
Reference in New Issue
Block a user