on master help sign in
Revert to using error calls in unlift functions for stack traces
f4f46b4Andrzej Rybczak
effectful-core/src/Effectful/Internal/Monad.hs−5
@@ -33,7 +33,6 @@ module Effectful.Internal.Monad
33 , unliftStrategy
33 , unliftStrategy
34 , withUnliftStrategy
34 , withUnliftStrategy
35 , withEffToIO
35 , withEffToIO
36− , UnliftError(..)
37
36
38 --- *** Low-level helpers
37 --- *** Low-level helpers
39 , seqUnliftIO
38 , seqUnliftIO
@@ -177,10 +176,6 @@ withEffToIO f = unliftStrategy >>= \case
177 unsafeEff $ \es -> concUnliftIO es p b f
176 unsafeEff $ \es -> concUnliftIO es p b f
178
177
179-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
178-- | Lower 'Eff' operations into 'IO' ('SeqUnlift').
180−--
181−-- Exceptions thrown by this function:
182−--
183−-- - 'InvalidUseOfSeqUnlift' if the unlift function is used in another thread.
184seqUnliftIO
179seqUnliftIO
185 :: HasCallStack
180 :: HasCallStack
186 => Env es
181 => Env es
effectful-core/src/Effectful/Internal/Unlift.hs+13−56
@@ -17,13 +17,9 @@ module Effectful.Internal.Unlift
17 , concUnlift
17 , concUnlift
18 , ephemeralConcUnlift
18 , ephemeralConcUnlift
19 , persistentConcUnlift
19 , persistentConcUnlift
20−
21− -- * Unlifting errors
22− , UnliftError(..)
23 ) where
20 ) where
24
21
25import Control.Concurrent
22import Control.Concurrent
26−import Control.Exception
27import Control.Monad
23import Control.Monad
28import GHC.Conc.Sync (ThreadId(..))
24import GHC.Conc.Sync (ThreadId(..))
29import GHC.Exts (mkWeak#, mkWeakNoFinalizer#)
25import GHC.Exts (mkWeak#, mkWeakNoFinalizer#)
@@ -96,10 +92,6 @@ data Limit
96-- Unlift functions
92-- Unlift functions
97
93
98-- | Sequential unlift.
94-- | Sequential unlift.
99−--
100−-- Exceptions thrown by this function:
101−--
102−-- - 'InvalidUseOfSeqUnlift' if the unlift function is used in another thread.
103seqUnlift
95seqUnlift
104 :: HasCallStack
96 :: HasCallStack
105 => ((forall r. m r -> IO r) -> IO a)
97 => ((forall r. m r -> IO r) -> IO a)
@@ -112,7 +104,9 @@ seqUnlift k es unEff = do
112 tid <- myThreadId
104 tid <- myThreadId
113 if tid `eqThreadId` tid0
105 if tid `eqThreadId` tid0
114 then unEff m es
106 then unEff m es
115− else throwIO InvalidUseOfSeqUnlift
107+ else error
108+ $ "If you want to use the unlifting function to run Eff operations "
109+ ++ "in multiple threads, have a look at UnliftStrategy (ConcUnlift)."
116
110
117-- | Concurrent unlift for various strategies and limits.
111-- | Concurrent unlift for various strategies and limits.
118concUnlift
112concUnlift
@@ -134,11 +128,6 @@ concUnlift Persistent Unlimited k =
134
128
135-- | Concurrent unlift that doesn't preserve the environment between calls to
129-- | Concurrent unlift that doesn't preserve the environment between calls to
136-- the unlifting function in threads other than its creator.
130-- the unlifting function in threads other than its creator.
137−--
138−-- Exceptions thrown by this function:
139−--
140−-- - 'InvalidNumberOfUses' if the number of uses is less or equal zero.
141−-- - 'ExceededNumberOfUses' if the unlift function is called too often.
142ephemeralConcUnlift
131ephemeralConcUnlift
143 :: HasCallStack
132 :: HasCallStack
144 => Int
133 => Int
@@ -149,7 +138,7 @@ ephemeralConcUnlift
149 -> IO a
138 -> IO a
150ephemeralConcUnlift uses k es0 unEff = do
139ephemeralConcUnlift uses k es0 unEff = do
151 unless (uses > 0) $ do
140 unless (uses > 0) $ do
152− throwIO $ InvalidNumberOfUses uses
141+ error $ "Invalid number of uses: " ++ show uses
153 tid0 <- myThreadId
142 tid0 <- myThreadId
154 -- Create a copy of the environment as a template for the other threads to
143 -- Create a copy of the environment as a template for the other threads to
155 -- use. This can't be done from inside the callback as the environment might
144 -- use. This can't be done from inside the callback as the environment might
@@ -160,7 +149,10 @@ ephemeralConcUnlift uses k es0 unEff = do
160 es <- myThreadId >>= \case
149 es <- myThreadId >>= \case
161 tid | tid0 `eqThreadId` tid -> pure es0
150 tid | tid0 `eqThreadId` tid -> pure es0
162 _ -> modifyMVar mvUses $ \case
151 _ -> modifyMVar mvUses $ \case
163− 0 -> throwIO $ ExceededNumberOfUses uses
152+ 0 -> error
153+ $ "Number of permitted calls (" ++ show uses ++ ") to the unlifting "
154+ ++ "function in other threads was exceeded. Please increase the limit "
155+ ++ "or use the unlimited variant."
164 1 -> pure (0, esTemplate)
156 1 -> pure (0, esTemplate)
165 n -> do
157 n -> do
166 let newUses = n - 1
158 let newUses = n - 1
@@ -170,12 +162,6 @@ ephemeralConcUnlift uses k es0 unEff = do
170
162
171-- | Concurrent unlift that preserves the environment between calls to the
163-- | Concurrent unlift that preserves the environment between calls to the
172-- unlifting function within a particular thread.
164-- unlifting function within a particular thread.
173−--
174−-- Exceptions thrown by this function:
175−--
176−-- - 'InvalidNumberOfThreads' if the number of threads is less or equal zero.
177−-- - 'ExceededNumberOfThreads' if the unlift function is called by too many
178−-- threads.
179persistentConcUnlift
165persistentConcUnlift
180 :: HasCallStack
166 :: HasCallStack
181 => Bool
167 => Bool
@@ -187,7 +173,7 @@ persistentConcUnlift
187 -> IO a
173 -> IO a
188persistentConcUnlift cleanUp threads k es0 unEff = do
174persistentConcUnlift cleanUp threads k es0 unEff = do
189 unless (threads > 0) $ do
175 unless (threads > 0) $ do
190− throwIO $ InvalidNumberOfThreads threads
176+ error $ "Invalid number of threads: " ++ show threads
191 tid0 <- myThreadId
177 tid0 <- myThreadId
192 -- Create a copy of the environment as a template for the other threads to
178 -- Create a copy of the environment as a template for the other threads to
193 -- use. This can't be done from inside the callback as the environment might
179 -- use. This can't be done from inside the callback as the environment might
@@ -205,7 +191,10 @@ persistentConcUnlift cleanUp threads k es0 unEff = do
205 case mes of
191 case mes of
206 Just es -> pure (te, es)
192 Just es -> pure (te, es)
207 Nothing -> case teCapacity te of
193 Nothing -> case teCapacity te of
208− 0 -> throwIO $ ExceededNumberOfThreads threads
194+ 0 -> error
195+ $ "Number of other threads (" ++ show threads ++ ") permitted to "
196+ ++ "use the unlifting function was exceeded. Please increase the "
197+ ++ "limit or use the unlimited variant."
209 1 -> do
198 1 -> do
210 wkTidEs <- mkWeakThreadIdEnv tid esTemplate wkTid i mvEntries cleanUp
199 wkTidEs <- mkWeakThreadIdEnv tid esTemplate wkTid i mvEntries cleanUp
211 let newEntries = ThreadEntries
200 let newEntries = ThreadEntries
@@ -223,38 +212,6 @@ persistentConcUnlift cleanUp threads k es0 unEff = do
223 newEntries `seq` pure (newEntries, es)
212 newEntries `seq` pure (newEntries, es)
224 unEff m es
213 unEff m es
225
214
226−----------------------------------------
227−-- Errors
228−
229−-- | An type used for errors occuring in unlifting functions.
230−--
231−-- Use the 'displayException' method of the 'Exception' type class to get
232−-- descriptive error messages.
233−data UnliftError
234− = InvalidNumberOfThreads !Int
235− | InvalidNumberOfUses !Int
236− | InvalidUseOfSeqUnlift
237− | ExceededNumberOfThreads !Int
238− | ExceededNumberOfUses !Int
239− deriving (Eq, Generic, Show)
240−
241−instance Exception UnliftError where
242− displayException (InvalidNumberOfThreads threads) =
243− "Invalid number of threads: " ++ show threads
244− displayException (InvalidNumberOfUses uses) =
245− "Invalid number of uses: " ++ show uses
246− displayException (ExceededNumberOfThreads threads) =
247− "Number of other threads (" ++ show threads ++ ") permitted to use the " ++
248− "unlifting function was exceeded. Please increase the limit or use the " ++
249− "unlimited variant."
250− displayException (ExceededNumberOfUses uses) =
251− "Number of permitted calls (" ++ show uses ++ ") to the unlifting " ++
252− "function in other threads was exceeded. Please increase the limit or " ++
253− "use the unlimited variant."
254− displayException InvalidUseOfSeqUnlift =
255− "If you want to use the unlifting function to run Eff operations in " ++
256− "multiple threads, have a look at UnliftStrategy (ConcUnlift)."
257−
258----------------------------------------
215----------------------------------------
259-- Data types
216-- Data types
260
217
effectful-core/src/Effectful/Monad.hs−1
@@ -18,7 +18,6 @@ module Effectful.Monad
18 , unliftStrategy
18 , unliftStrategy
19 , withUnliftStrategy
19 , withUnliftStrategy
20 , withEffToIO
20 , withEffToIO
21− , UnliftError(..)
22
21
23 -- * Re-exports
22 -- * Re-exports
24 , MonadIO(..)
23 , MonadIO(..)
effectful/effectful.cabal−1
@@ -118,7 +118,6 @@ test-suite test
118 , effectful-core
118 , effectful-core
119 , exceptions
119 , exceptions
120 , lifted-base
120 , lifted-base
121− , optics-core >= 0.4
122 , tasty
121 , tasty
123 , tasty-hunit
122 , tasty-hunit
124 , unliftio
123 , unliftio
effectful/src/Effectful.hs−1
@@ -22,7 +22,6 @@ module Effectful
22 , unliftStrategy
22 , unliftStrategy
23 , withUnliftStrategy
23 , withUnliftStrategy
24 , withEffToIO
24 , withEffToIO
25− , UnliftError(..)
26
25
27 -- * 'Effect' handlers
26 -- * 'Effect' handlers
28
27
effectful/tests/UnliftTests.hs+28−34
@@ -1,7 +1,7 @@
1{-# LANGUAGE OverloadedLabels #-}
1{-# LANGUAGE OverloadedLabels #-}
2module UnliftTests (unliftTests) where
2module UnliftTests (unliftTests) where
3
3
4−import Optics.Core
4+import Control.Exception
5import Test.Tasty
5import Test.Tasty
6import Test.Tasty.HUnit
6import Test.Tasty.HUnit
7import qualified UnliftIO.Async as A
7import qualified UnliftIO.Async as A
@@ -34,43 +34,38 @@ test_resetStrategy = runEff $ do
34
34
35test_seqUnliftInNewThread :: Assertion
35test_seqUnliftInNewThread :: Assertion
36test_seqUnliftInNewThread = runEff $ do
36test_seqUnliftInNewThread = runEff $ do
37− assertThrowsUnliftError "InvalidUseOfSeqUnlift error"
37+ assertThrowsUnliftError "InvalidUseOfSeqUnlift error" $ do
38− (has #_InvalidUseOfSeqUnlift) $ do
39− withUnliftStrategy SeqUnlift $ do
38+ withUnliftStrategy SeqUnlift $ do
40− withEffToIO $ \runInIO -> do
39+ withEffToIO $ \runInIO -> do
41− inThread $ runInIO $ return ()
40+ inThread $ runInIO $ return ()
42
41
43test_ephemeralInvalid :: Assertion
42test_ephemeralInvalid :: Assertion
44test_ephemeralInvalid = runEff $ do
43test_ephemeralInvalid = runEff $ do
45− assertThrowsUnliftError "InvalidNumberOfUses error"
44+ assertThrowsUnliftError "InvalidNumberOfUses error" $ do
46− (has #_InvalidNumberOfUses) $ do
47− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 0) $ do
45+ withUnliftStrategy (ConcUnlift Ephemeral $ Limited 0) $ do
48− withEffToIO $ \_ -> return ()
46+ withEffToIO $ \_ -> return ()
49
47
50test_ephemeralSameThread :: Assertion
48test_ephemeralSameThread :: Assertion
51test_ephemeralSameThread = runEff $ do
49test_ephemeralSameThread = runEff $ do
52− assertThrowsUnliftError "ExceededNumberOfUses error"
50+ assertThrowsUnliftError "ExceededNumberOfUses error" $ do
53− (has #_ExceededNumberOfUses) $ do
54− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
51+ withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
55− withEffToIO $ \runInIO -> inThread $ do
52+ withEffToIO $ \runInIO -> inThread $ do
56− runInIO $ return ()
53+ runInIO $ return ()
57− runInIO $ return ()
54+ runInIO $ return ()
58
55
59test_ephemeralMultipleThreads :: Assertion
56test_ephemeralMultipleThreads :: Assertion
60test_ephemeralMultipleThreads = runEff $ do
57test_ephemeralMultipleThreads = runEff $ do
61− assertThrowsUnliftError "ExceededNumberOfUses error"
58+ assertThrowsUnliftError "ExceededNumberOfUses error" $ do
62− (has #_ExceededNumberOfUses) $ do
63− withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
59+ withUnliftStrategy (ConcUnlift Ephemeral $ Limited 1) $ do
64− withEffToIO $ \runInIO -> do
60+ withEffToIO $ \runInIO -> do
65− inThread $ runInIO $ return ()
61+ inThread $ runInIO $ return ()
66− inThread $ runInIO $ return ()
62+ inThread $ runInIO $ return ()
67
63
68test_persistentInvalid :: Assertion
64test_persistentInvalid :: Assertion
69test_persistentInvalid = runEff $ do
65test_persistentInvalid = runEff $ do
70− assertThrowsUnliftError "InvalidNumberOfThreads error"
66+ assertThrowsUnliftError "InvalidNumberOfThreads error" $ do
71− (has #_InvalidNumberOfThreads) $ do
72− withUnliftStrategy (ConcUnlift Persistent $ Limited 0) $ do
67+ withUnliftStrategy (ConcUnlift Persistent $ Limited 0) $ do
73− withEffToIO $ \_ -> return ()
68+ withEffToIO $ \_ -> return ()
74
69
75test_persistentSameThread :: Assertion
70test_persistentSameThread :: Assertion
76test_persistentSameThread = runEff $ do
71test_persistentSameThread = runEff $ do
@@ -81,20 +76,19 @@ test_persistentSameThread = runEff $ do
81
76
82test_persistentMultipleThreads :: Assertion
77test_persistentMultipleThreads :: Assertion
83test_persistentMultipleThreads = runEff $ do
78test_persistentMultipleThreads = runEff $ do
84− assertThrowsUnliftError "ExceededNumberOfThreads error"
79+ assertThrowsUnliftError "ExceededNumberOfThreads error" $ do
85− (has #_ExceededNumberOfThreads) $ do
86− withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
80+ withUnliftStrategy (ConcUnlift Persistent $ Limited 1) $ do
87− withEffToIO $ \runInIO -> do
81+ withEffToIO $ \runInIO -> do
88− inThread $ runInIO $ return ()
82+ inThread $ runInIO $ return ()
89− inThread $ runInIO $ return ()
83+ inThread $ runInIO $ return ()
90
84
91----------------------------------------
85----------------------------------------
92-- Helpers
86-- Helpers
93
87
94assertThrowsUnliftError
88assertThrowsUnliftError
95 :: IOE :> es
89 :: IOE :> es
96− => String -> (UnliftError -> Bool) -> Eff es a -> Eff es ()
90+ => String -> Eff es a -> Eff es ()
97−assertThrowsUnliftError = U.assertThrows
91+assertThrowsUnliftError err = U.assertThrows err (\ErrorCall{} -> True)
98
92
99inThread :: IO a -> IO a
93inThread :: IO a -> IO a
100inThread k = A.async k >>= A.wait
94inThread k = A.async k >>= A.wait