@@ -21,6 +21,7 @@ import Control.Concurrent.STM (TChan, atomically, newTChan,
21import Control.Exception (ErrorCall (..),
21import Control.Exception (ErrorCall (..),
22 SomeException (..), catch)
22 SomeException (..), catch)
23import qualified Control.Exception.Lifted as EL
23import qualified Control.Exception.Lifted as EL
24+import Control.Monad.Catch (MonadThrow, throwM)
24import Control.Monad.IO.Class (MonadIO (..))
25import Control.Monad.IO.Class (MonadIO (..))
25import Control.Monad.State (get, gets, modify, put,
26import Control.Monad.State (get, gets, modify, put,
26 runStateT, when)
27 runStateT, when)
@@ -71,7 +72,7 @@ import Duct.Context
71resume :: Monad m => Context -> m (Maybe a)
72resume :: Monad m => Context -> m (Maybe a)
72resume ctx = do
73resume ctx = do
73 let s = runAsyncT (resumeContext ctx)
74 let s = runAsyncT (resumeContext ctx)
74− (a, s) <- runStateT s ctx
75+ (a, _) <- runStateT s ctx
77updateContextEvent :: Context -> a -> Context
78updateContextEvent :: Context -> a -> Context
@@ -81,25 +82,114 @@ updateContextEvent ctx evdata = ctx { event = Just $ unsafeCoerce evdata }
81-- Thread Management (creation, reaping and killing)
82-- Thread Management (creation, reaping and killing)
82------------------------------------------------------------------------------
83------------------------------------------------------------------------------
84−tryReclaimZombies :: MonadIO m => TChan ThreadId -> IORef [ThreadId] -> m ()
85+-- XXX We are using unbounded channels so this will not block on writing to
86+-- pchan. We can use bounded channels to throttle the creation of threads based
87+-- on consumption rate.
88+processOneEvent :: MonadIO m
90+ -> TChan (ChildEvent a)
92+ -> Maybe SomeException
93+ -> m ([ThreadId], Maybe SomeException)
94+processOneEvent ev pchan pending exc = do
98+ ChildDone tid res -> do
99+ dbg $ "Reaping child: " ++ show tid
101+ PassOnResult res -> handlePass res
102+ Just _ -> return exc
104+ ChildDone tid _ -> delete tid pending
110+ handleDone :: MonadIO m
111+ => Either SomeException (Maybe a) -> m (Maybe SomeException)
115+ liftIO $ mapM_ killThread pending
119+ Nothing -> return ()
120+ Just x -> liftIO $ atomically
122+ (PassOnResult (Right (unsafeCoerce x)))
125+ handlePass :: MonadIO m
126+ => Either SomeException a -> m (Maybe SomeException)
130+ liftIO $ mapM_ killThread pending
133+ liftIO $ atomically $ writeTChan pchan
134+ (PassOnResult (unsafeCoerce res))
137+tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
85−tryReclaimZombies chan pendingRef = do
138+tryReclaimZombies ctx = do
139+ let pchan = fromJust (parentChannel ctx)
140+ cchan = childChannel ctx
141+ pendingRef = pendingThreads ctx
86 pending <- liftIO $ readIORef pendingRef
143 pending <- liftIO $ readIORef pendingRef
87 case pending of
144 case pending of
88 [] -> return ()
145 [] -> return ()
90− mtid <- liftIO $ atomically $ tryReadTChan chan
147+ mev <- liftIO $ atomically $ tryReadTChan cchan
91− case mtid of
148+ case mev of
92 Nothing -> return ()
149 Nothing -> return ()
93− Just tid -> do
150+ Just ev -> do
151+ (p, e) <- processOneEvent ev pchan pending Nothing
94− liftIO $ writeIORef pendingRef $ delete tid pending
152+ liftIO $ writeIORef pendingRef p
153+ maybe (return ()) throwM e
95− tryReclaimZombies chan pendingRef
154+ tryReclaimZombies ctx
97−waitForOneChild :: MonadIO m => TChan ThreadId -> IORef [ThreadId] -> m ()
156+waitForOneEvent :: (MonadIO m, MonadThrow m) => Context -> m ()
98−waitForOneChild chan pendingRef = do
157+waitForOneEvent ctx = do
99 -- XXX assert pending must have at least one element
158 -- XXX assert pending must have at least one element
100 -- assert that the tid is found in our list
159 -- assert that the tid is found in our list
160+ let pchan = fromJust (parentChannel ctx)
161+ cchan = childChannel ctx
162+ pendingRef = pendingThreads ctx
164+ ev <- liftIO $ atomically $ readTChan cchan
165+ pending <- liftIO $ readIORef pendingRef
166+ (p, e) <- processOneEvent ev pchan pending Nothing
167+ liftIO $ writeIORef pendingRef p
168+ maybe (return ()) throwM e
170+drainChildren :: MonadIO m
171+ => TChan (ChildEvent a)
172+ -> TChan (ChildEvent a)
174+ -> Maybe SomeException
175+ -> m (Maybe SomeException)
176+drainChildren pchan cchan pending exc =
101− tid <- liftIO $ atomically $ readTChan chan
180+ ev <- liftIO $ atomically $ readTChan cchan
181+ (p, e) <- processOneEvent ev pchan pending exc
182+ drainChildren pchan cchan p e
184+waitForChildren :: MonadIO m
185+ => Context -> Maybe SomeException -> m (Maybe SomeException)
186+waitForChildren ctx exc = do
187+ let pendingRef = pendingThreads ctx
188+ pchan = fromJust (parentChannel ctx)
102− liftIO $ modifyIORef pendingRef $ delete tid
189+ pending <- liftIO $ readIORef pendingRef
190+ e <- drainChildren pchan (childChannel ctx) pending exc
191+ liftIO $ writeIORef pendingRef []
104-- | kill all the child threads associated with the continuation context
194-- | kill all the child threads associated with the continuation context
105killChildren :: Context -> IO ()
195killChildren :: Context -> IO ()
@@ -148,7 +238,7 @@ forkFinally1 action and_then =
149-- | Run a given context in a new thread.
239-- | Run a given context in a new thread.
151−forkContextWith :: (MonadBaseControl IO m, MonadIO m)
241+forkContextWith :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
152 => (Context -> m (Maybe a)) -> Context -> m ()
242 => (Context -> m (Maybe a)) -> Context -> m ()
153forkContextWith runCtx context = do
243forkContextWith runCtx context = do
154 child <- childContext context
244 child <- childContext context
@@ -157,12 +247,13 @@ forkContextWith runCtx context = do
160− updatePendingThreads :: MonadIO m => Context -> ThreadId -> m ()
250+ updatePendingThreads :: (MonadIO m, MonadThrow m)
251+ => Context -> ThreadId -> m ()
161− updatePendingThreads cur tid = do
252+ updatePendingThreads ctx tid = do
162 -- update the new thread before reclaiming zombies so that if it exited
253 -- update the new thread before reclaiming zombies so that if it exited
163 -- already reclaim finds it in the list and does not panic.
254 -- already reclaim finds it in the list and does not panic.
164− liftIO $ modifyIORef (pendingThreads cur) $ (\ts -> tid:ts)
255+ liftIO $ modifyIORef (pendingThreads ctx) $ (\ts -> tid:ts)
165− tryReclaimZombies (childChannel cur) (pendingThreads cur)
256+ tryReclaimZombies ctx
167 childContext ctx = do
258 childContext ctx = do
168 pendingRef <- liftIO $ newIORef []
259 pendingRef <- liftIO $ newIORef []
@@ -174,25 +265,27 @@ forkContextWith runCtx context = do
174 , childChannel = chan
265 , childChannel = chan
177− beforeExit cur res = do
268+ beforeExit ctx res = do
178− case res of
269+ exc <- case res of
179− Left _exc -> liftIO $ killChildren cur
271+ liftIO $ killChildren ctx
180− -- propagate the exception to the parent
181− Right _ -> -- collect the results?
182− return ()
272+ return (Just e)
273+ Right _ -> return Nothing
184− waitForChildren (childChannel cur) (pendingThreads cur)
275+ e <- waitForChildren ctx exc
185− signalQSemB (threadCredit cur)
276+
186 -- We are guaranteed to have a parent because we have been explicitly
278 -- We are guaranteed to have a parent because we have been explicitly
187 -- forked by some parent.
279 -- forked by some parent.
188− let p = fromJust (parentChannel cur)
280+ let p = fromJust (parentChannel ctx)
189− tid <- myThreadId
281+ signalQSemB (threadCredit ctx)
190− liftIO $ atomically $ writeTChan p tid
282+ liftIO $ atomically $ writeTChan p
283+ (ChildDone tid (maybe (unsafeCoerce res) Left e))
192-- | A wrapper to first decide whether to run the context in the same thread or
285-- | A wrapper to first decide whether to run the context in the same thread or
193-- a new thread and then run the context in that thread.
286-- a new thread and then run the context in that thread.
195−resumeContextWith :: (MonadBaseControl IO m, MonadIO m)
288+resumeContextWith :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
196 => (Context -> m (Maybe a)) -> Context -> m ()
289 => (Context -> m (Maybe a)) -> Context -> m ()
197resumeContextWith runCtx context = do
290resumeContextWith runCtx context = do
198 gotCredit <- liftIO $ waitQSemB (threadCredit context)
291 gotCredit <- liftIO $ waitQSemB (threadCredit context)
@@ -208,8 +301,7 @@ resumeContextWith runCtx context = do
208 -- special handling for those cases. Add those to
301 -- special handling for those cases. Add those to
209 -- unreclaimable list? And always execute them in an
302 -- unreclaimable list? And always execute them in an
210 -- async thread, cannot use sync for those.
303 -- async thread, cannot use sync for those.
211− waitForOneChild (childChannel context)
304+ waitForOneEvent context
212− (pendingThreads context)
213 resumeContextWith runCtx context
305 resumeContextWith runCtx context
214 True -> forkContextWith runCtx context
306 True -> forkContextWith runCtx context
@@ -238,7 +330,7 @@ data StreamData a =
239-- | Execute the IO action, resume the saved context with the output of the io
331-- | Execute the IO action, resume the saved context with the output of the io
240-- action.
332-- action.
241−loopContextWith :: (MonadIO m, MonadBaseControl IO m)
333+loopContextWith :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
242 => IO (StreamData t) -> Context -> m (Maybe a)
334 => IO (StreamData t) -> Context -> m (Maybe a)
243loopContextWith ioaction ctx = do
335loopContextWith ioaction ctx = do
244 streamData <- liftIO $ ioaction `catch`
336 streamData <- liftIO $ ioaction `catch`
@@ -260,7 +352,7 @@ loopContextWith ioaction ctx = do
260-- Unless the maximum number of threads (set with 'threads') has been reached,
352-- Unless the maximum number of threads (set with 'threads') has been reached,
261-- the task is generated in a new thread and the current thread returns a void
353-- the task is generated in a new thread and the current thread returns a void
263−parallel :: (Monad m, MonadIO m, MonadBaseControl IO m)
355+parallel :: (Monad m, MonadIO m, MonadBaseControl IO m, MonadThrow m)
264 => IO (StreamData a) -> AsyncT m (StreamData a)
356 => IO (StreamData a) -> AsyncT m (StreamData a)
265parallel ioaction = AsyncT $ do
357parallel ioaction = AsyncT $ do
266 -- We retrieve the context here and pass it on so that we can resume it
358 -- We retrieve the context here and pass it on so that we can resume it
@@ -291,7 +383,8 @@ parallel ioaction = AsyncT $ do
291-- | An task stream generator that produces an infinite stream of tasks by
383-- | An task stream generator that produces an infinite stream of tasks by
292-- running an IO computation in a loop. A task is triggered carrying the output
384-- running an IO computation in a loop. A task is triggered carrying the output
293-- of the computation. See 'parallel' for notes on the return value.
385-- of the computation. See 'parallel' for notes on the return value.
294−waitEvents :: (MonadIO m, MonadBaseControl IO m) => IO a -> AsyncT m a
386+waitEvents :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
387+ => IO a -> AsyncT m a
295waitEvents io = do
388waitEvents io = do
296 mr <- parallel (SMore <$> io)
389 mr <- parallel (SMore <$> io)
297 case mr of
390 case mr of
@@ -301,7 +394,8 @@ waitEvents io = do
301-- | Run an IO computation asynchronously and generate a single task carrying
394-- | Run an IO computation asynchronously and generate a single task carrying
302-- the result of the computation when it completes. See 'parallel' for notes on
395-- the result of the computation when it completes. See 'parallel' for notes on
303-- the return value.
396-- the return value.
304−async :: (MonadIO m, MonadBaseControl IO m) => IO a -> AsyncT m a
397+async :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
398+ => IO a -> AsyncT m a
305async io = do
399async io = do
306 mr <- parallel (SLast <$> io)
400 mr <- parallel (SLast <$> io)
307 case mr of
401 case mr of
@@ -324,7 +418,8 @@ sync x = AsyncT $ do
324-- task carries the result of the computation. A new task is generated only if
418-- task carries the result of the computation. A new task is generated only if
325-- the output of the computation is different from the previous one. See
419-- the output of the computation is different from the previous one. See
326-- 'parallel' for notes on the return value.
420-- 'parallel' for notes on the return value.
327−sample :: (Eq a, MonadIO m, MonadBaseControl IO m) => IO a -> Int -> AsyncT m a
421+sample :: (Eq a, MonadIO m, MonadBaseControl IO m, MonadThrow m)
422+ => IO a -> Int -> AsyncT m a
328sample action interval = do
423sample action interval = do
329 v <- liftIO action
424 v <- liftIO action
330 prev <- liftIO $ newIORef v
425 prev <- liftIO $ newIORef v