on master help sign in
Initial commit

A simplified, refactored and modified version of transient. Significant differences from transient include: 1) Supports transformer stack 2) Each thread waits for its child threads to complete before exiting

commitb90da15d00303d842f467ea5e7d5f00dd70acc49 authorHarendra Kumar <harendra.kumar@gmail.com> dateWed 7 Jun 2017, 21:02 (9 years ago)
9 files changed+1237
LICENSE+19 README.md+2 duct.cabal+46 src/Duct.hs+20 src/Duct/AsyncT.hs+545 src/Duct/Event.hs+150 src/Duct/Log.hs+54 src/Duct/Threads.hs+394 stack.yaml+7
LICENSEnew file+19
@@ -0,0 +1,19 @@
1+Copyright © 2017 Harendra Kumar
2+Copyright © 2014-2017 Alberto G. Corona <https://github.com/agocorona>
3+
4+Permission is hereby granted, free of charge, to any person obtaining a copy of
5+this software and associated documentation files (the "Software"), to deal in
6+the Software without restriction, including without limitation the rights to
7+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8+the Software, and to permit persons to whom the Software is furnished to do so,
9+subject to the following conditions:
10+
11+The above copyright notice and this permission notice shall be included in all
12+copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.mdnew file+2
@@ -0,0 +1,2 @@
1+This is an experimental package derived from the transient package by Alberto
2+G. Corona.
duct.cabalnew file+46
@@ -0,0 +1,46 @@
1+name: duct
2+version: 0.5.5
3+synopsis: A duct consists of many parallel (multithreaded) data pipes
4+description: An experimental package based on the transient package. Provides a
5+ simple interface to integrate with other libraries and monad transformers.
6+ See <http://github.com/agocorona/transient>
7+homepage: http://github.com/harendra-kumar/duct
8+bug-reports: https://github.com/harendra-kumar/duct/issues
9+license: MIT
10+license-file: LICENSE
11+author: Alberto G. Corona, Harendra Kumar
12+maintainer: harendra.kumar@gmail.com
13+copyright: 2017 Harendra Kumar,
14+ 2014–2017 Alberto G. Corona
15+category: Parallel, Distributed
16+stability: Experimental
17+build-type: Simple
18+cabal-version: >= 1.10
19+
20+library
21+ hs-source-dirs: src
22+ exposed-modules: Duct
23+ , Duct.Event
24+ , Duct.Log
25+ , Duct.AsyncT
26+ , Duct.Threads
27+
28+ default-language: Haskell2010
29+ ghc-options: -Wall -fwarn-identities -fwarn-incomplete-record-updates
30+ -fwarn-incomplete-uni-patterns -fwarn-tabs
31+
32+ build-depends: base >= 4.8.1 && < 5
33+ , containers >= 0.5.6
34+ , time >= 1.5
35+ , transformers >= 0.4.2
36+
37+ -- libraries not bundled with GHC
38+ , lifted-base
39+ , monad-control
40+ , mtl
41+ , stm
42+ , transformers-base
43+
44+source-repository head
45+ type: git
46+ location: https://github.com/harendra-kumar/duct
src/Duct.hsnew file+20
@@ -0,0 +1,20 @@
1+-- |
2+-- Module : Duct
3+-- Copyright : (c) 2017 Harendra Kumar
4+--
5+-- License : MIT-style
6+-- Maintainer : harendra.kumar@gmail.com
7+-- Stability : experimental
8+-- Portability : GHC
9+--
10+
11+module Duct
12+ ( waitAsync
13+ , async
14+ , sample
15+ , threads
16+ )
17+where
18+
19+import Duct.AsyncT
20+import Duct.Threads
src/Duct/AsyncT.hsnew file+545
@@ -0,0 +1,545 @@
1+{-# LANGUAGE ExistentialQuantification #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE InstanceSigs #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
6+{-# LANGUAGE Rank2Types #-}
7+{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeFamilies #-}
10+{-# LANGUAGE UndecidableInstances #-}
11+
12+module Duct.AsyncT
13+ ( AsyncT (..)
14+ , waitAsync
15+ , compose
16+ , (<**)
17+ , onNothing
18+ , RemoteStatus (..)
19+ , back
20+ , waitForChildren
21+ , dbg
22+ )
23+where
24+
25+import Control.Applicative (Alternative (..))
26+import Control.Concurrent (ThreadId, myThreadId)
27+import Control.Concurrent.STM (TChan, atomically, newTChan,
28+ readTChan)
29+import Control.Exception (SomeException, catch)
30+import Control.Monad.Base (MonadBase (..), liftBaseDefault)
31+import Control.Monad.State (MonadIO (..), MonadPlus (..),
32+ MonadState (..), StateT (..),
33+ liftM, modify, runStateT, when)
34+import Control.Monad.Trans.Class (MonadTrans (lift))
35+import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
36+ MonadTransControl (..),
37+ defaultLiftBaseWith,
38+ defaultRestoreM)
39+import Data.Dynamic (Typeable)
40+import Data.IORef (IORef, newIORef, readIORef,
41+ writeIORef)
42+import Data.Maybe (isJust, isNothing)
43+import Unsafe.Coerce (unsafeCoerce)
44+
45+import Duct.Event
46+import Duct.Log
47+
48+-- import Debug.Trace
49+
50+type StateM m = StateT EventF m
51+
52+newtype AsyncT m a = AsyncT { runAsyncT :: StateM m (Maybe a) }
53+
54+------------------------------------------------------------------------------
55+-- Remote data types
56+------------------------------------------------------------------------------
57+
58+data RemoteStatus = WasRemote | WasParallel | NoRemote
59+ deriving (Typeable, Eq, Show)
60+
61+------------------------------------------------------------------------------
62+-- Utilities
63+------------------------------------------------------------------------------
64+
65+-- dbg x = trace x (return ())
66+dbg :: Monad m => a -> m ()
67+dbg _ = return ()
68+
69+-- | If the first parameter is 'Nothing' return the second parameter otherwise
70+-- return the first parameter..
71+onNothing :: Monad m => m (Maybe b) -> m b -> m b
72+onNothing iox iox'= do
73+ mx <- iox
74+ case mx of
75+ Just x -> return x
76+ Nothing -> iox'
77+
78+------------------------------------------------------------------------------
79+-- Functor
80+------------------------------------------------------------------------------
81+
82+instance Monad (AsyncT m) => Functor (AsyncT m) where
83+ fmap f mx = do
84+ x <- mx
85+ return $ f x
86+
87+------------------------------------------------------------------------------
88+-- Applicative
89+------------------------------------------------------------------------------
90+
91+-- | Warning: Radically untyped stuff. handle with care
92+getContinuations :: Monad m => StateM m [a -> AsyncT m b]
93+getContinuations = do
94+ EventF { fcomp = fs } <- get
95+ return $ unsafeCoerce fs
96+
97+-- | Save a closure and a continuation ('x' and 'f' in 'x >>= f').
98+setContinuation :: Monad m
99+ => AsyncT m a -> (a -> AsyncT m b) -> [c -> AsyncT m c] -> StateM m ()
100+setContinuation b c fs = do
101+ modify $ \EventF{..} -> EventF { xcomp = b
102+ , fcomp = unsafeCoerce c : fs
103+ , .. }
104+
105+-- | Restore the continuations to the provided ones.
106+-- | NOTE: Events are also cleared out.
107+restoreStack :: MonadState EventF m => t -> m ()
108+restoreStack fs = modify $ \EventF {..} ->
109+ EventF { event = Nothing, fcomp = (unsafeCoerce fs), .. }
110+
111+instance MonadIO m => Applicative (AsyncT m) where
112+ pure a = AsyncT . return $ Just a
113+
114+ f <*> g = AsyncT $ do
115+ rf <- liftIO $ newIORef (Nothing,[])
116+ rg <- liftIO $ newIORef (Nothing,[])
117+ fs <- getContinuations
118+
119+ let hasWait (_:Wait:_) = True
120+ hasWait _ = False
121+
122+ appf k = AsyncT $ do
123+ Log rec _ full <- getData `onNothing` return (Log False [] [])
124+ (liftIO $ writeIORef rf (Just k,full))
125+ (x, full2)<- liftIO $ readIORef rg
126+ when (hasWait full ) $
127+ let full'= head full: full2
128+ in (setData $ Log rec full' full')
129+ return $ Just (unsafeCoerce k) <*> x
130+
131+ appg x = AsyncT $ do
132+ Log rec _ full <- getData `onNothing` return (Log False [] [])
133+ liftIO $ writeIORef rg (Just x, full)
134+ (k,full1) <- liftIO $ readIORef rf
135+ when (hasWait full) $
136+ let full'= head full: full1
137+ in (setData $ Log rec full' full')
138+ return $ unsafeCoerce k <*> Just x
139+
140+ setContinuation f appf fs
141+
142+ k <- runAsyncT f
143+ was <- getData `onNothing` return NoRemote
144+ when (was == WasParallel) $ setData NoRemote
145+
146+ Log recovery _ full <- getData `onNothing` return (Log False [] [])
147+
148+
149+
150+ if was== WasRemote || (not recovery && was == NoRemote && isNothing k )
151+ -- if the first operand was a remote request
152+ -- (so this node is not master and hasn't to execute the whole expression)
153+ -- or it was not an asyncronous term (a normal term without async or parallel
154+ -- like primitives) and is nothing
155+ then do
156+ restoreStack fs
157+ return Nothing
158+ else do
159+ when (isJust k) $ liftIO $ writeIORef rf (k,full)
160+ -- when necessary since it maybe WasParallel and Nothing
161+
162+ setContinuation g appg fs
163+
164+ x <- runAsyncT g
165+ Log _ _ full' <- getData `onNothing` return (Log False [] [])
166+ liftIO $ writeIORef rg (x,full')
167+ restoreStack fs
168+ k'' <- if was== WasParallel
169+ then do
170+ (k',_) <- liftIO $ readIORef rf -- since k may have been updated by a parallel f
171+ return k'
172+ else return k
173+ return $ k'' <*> x
174+
175+------------------------------------------------------------------------------
176+-- Alternative
177+------------------------------------------------------------------------------
178+
179+instance MonadIO m => Alternative (AsyncT m) where
180+ empty = AsyncT $ return Nothing
181+ (<|>) x y = AsyncT $ do
182+ mx <- runAsyncT x
183+ was <- getData `onNothing` return NoRemote
184+ if was == WasRemote
185+ then return Nothing
186+ else case mx of
187+ Nothing -> runAsyncT y
188+ justx -> return justx
189+
190+-- | A synonym of 'empty' that can be used in a monadic expression. It stops
191+-- the computation, which allows the next computation in an 'Alternative'
192+-- ('<|>') composition to run.
193+stop :: Alternative m => m stopped
194+stop = empty
195+
196+------------------------------------------------------------------------------
197+-- Monad
198+------------------------------------------------------------------------------
199+
200+-- | Total variant of `tail` that returns an empty list when given an empty list.
201+tailsafe :: [a] -> [a]
202+tailsafe [] = []
203+tailsafe (_:xs) = xs
204+
205+-- | Set the current closure and continuation for the current statement
206+setEventCont :: Monad m => AsyncT m a -> (a -> AsyncT m b) -> StateM m EventF
207+setEventCont x f = do
208+ EventF { fcomp = fs, .. } <- get
209+ let cont = EventF { xcomp = x
210+ , fcomp = unsafeCoerce f : unsafeCoerce fs
211+ , .. }
212+ put cont
213+ return cont
214+
215+-- | Reset the closure and continuation. Remove inner binds that the previous
216+-- computations may have stacked in the list of continuations.
217+resetEventCont :: forall m a b. MonadIO m
218+ => Maybe a -> EventF -> StateM m (AsyncT m b -> AsyncT m b)
219+resetEventCont mx _ = do
220+ EventF { fcomp = fs, .. } <- get
221+ let f my = case my of
222+ Nothing -> (empty :: AsyncT m a)
223+ Just x -> unsafeCoerce (head fs) x
224+ put $ EventF { xcomp = f mx
225+ , fcomp = tailsafe (unsafeCoerce fs)
226+ , .. }
227+ return id
228+
229+instance MonadIO m => Monad (AsyncT m) where
230+ return = pure
231+ x >>= f = AsyncT $ do
232+ c <- setEventCont x f
233+ mk <- runAsyncT x
234+ _ <- resetEventCont mk c
235+ case mk of
236+ Just k -> runAsyncT (f k)
237+ Nothing -> return Nothing
238+
239+instance MonadIO m => MonadPlus (AsyncT m) where
240+ mzero = empty
241+ mplus = (<|>)
242+
243+instance (Monoid a, Monad (AsyncT m)) => Monoid (AsyncT m a) where
244+ mappend x y = mappend <$> x <*> y
245+ mempty = return mempty
246+
247+------------------------------------------------------------------------------
248+-- Backtracking
249+------------------------------------------------------------------------------
250+
251+-- | Run the closure (the 'x' in 'x >>= f') of the current bind operation.
252+runClosure :: EventF -> StateM m (Maybe a)
253+runClosure EventF { xcomp = x } = runAsyncT (unsafeCoerce x)
254+
255+-- | Compose a list of continuations.
256+compose :: MonadIO m => [a -> AsyncT m a] -> (a -> AsyncT m b)
257+compose [] = const empty
258+compose (f:fs) = \x -> f x >>= compose fs
259+
260+-- | Run the continuation (the 'f' in 'x >>= f') of the current bind operation with the current state.
261+runContinuation :: MonadIO m => EventF -> a -> StateM m (Maybe b)
262+runContinuation EventF { fcomp = fs } =
263+ runAsyncT . (compose $ (unsafeCoerce fs))
264+
265+data Backtrack b = Show b => Backtrack
266+ { backtracking :: Maybe b
267+ , backStack :: [EventF]
268+ } deriving Typeable
269+
270+backStateOf :: (Monad m, Show a) => a -> m (Backtrack a)
271+backStateOf reason = return $ Backtrack (Nothing `asTypeOf` (Just reason)) []
272+
273+-- | Start the undo process for the given undo track id. Performs all the undo
274+-- actions registered till now in reverse order. An undo action can use
275+-- 'forward' to stop the undo process and resume forward execution. If there
276+-- are no more undo actions registered execution stops and a 'stop' action is
277+-- returned.
278+--
279+back :: (MonadIO m, Typeable b, Show b) => b -> AsyncT m a
280+back reason = AsyncT $ do
281+ bs <- getData `onNothing` backStateOf reason
282+ goBackt bs
283+
284+ where
285+
286+ goBackt (Backtrack _ [] )= return Nothing
287+ goBackt (Backtrack _ (stack@(first : bs)) )= do
288+ (setData $ Backtrack (Just reason) stack)
289+
290+ mr <- runClosure first
291+
292+ Backtrack b _ <- getData `onNothing` backStateOf reason
293+ case mr of
294+ Nothing -> return empty
295+ Just x -> case b of
296+ Nothing -> runContinuation first x
297+ justreason -> goBackt $ Backtrack justreason bs
298+
299+------------------------------------------------------------------------------
300+-- MonadIO
301+------------------------------------------------------------------------------
302+
303+instance MonadIO m => MonadIO (AsyncT m) where
304+ liftIO mx = do
305+ ex <- liftIO' $ (mx >>= return . Right) `catch`
306+ (\(e :: SomeException) -> return $ Left e)
307+ case ex of
308+ Left e -> back e
309+ Right x -> return x
310+ where liftIO' x = AsyncT $ liftIO x >>= return . Just
311+
312+-------------------------------------------------------------------------------
313+-- AsyncT transformer
314+-------------------------------------------------------------------------------
315+
316+instance MonadTrans AsyncT where
317+ lift mx = AsyncT $ lift mx >>= return . Just
318+
319+instance MonadTransControl AsyncT where
320+ type StT AsyncT a = (Maybe a, EventF)
321+ liftWith f = AsyncT $ StateT $ \s ->
322+ liftM (\x -> (Just x, s))
323+ (f $ \t -> runStateT (runAsyncT t) s)
324+ restoreT = AsyncT . StateT . const
325+ {-# INLINABLE liftWith #-}
326+ {-# INLINABLE restoreT #-}
327+
328+instance (MonadBase b m, MonadIO m) => MonadBase b (AsyncT m) where
329+ liftBase = liftBaseDefault
330+
331+instance (MonadBaseControl b m, MonadIO m) => MonadBaseControl b (AsyncT m) where
332+ type StM (AsyncT m) a = ComposeSt AsyncT m a
333+ liftBaseWith = defaultLiftBaseWith
334+ restoreM = defaultRestoreM
335+ {-# INLINABLE liftBaseWith #-}
336+ {-# INLINABLE restoreM #-}
337+
338+------------------------------------------------------------------------------
339+-- Thread management
340+------------------------------------------------------------------------------
341+
342+drainChildren :: MonadIO m => TChan ThreadId -> [EventF] -> m ()
343+drainChildren chan pending =
344+ if length pending == 0
345+ then return ()
346+ else do
347+ tid <- liftIO $ atomically $ readTChan chan
348+ dbg $ "Reaping child: " ++ show tid
349+ drainChildren chan $ filter (\x -> threadId x /= tid) pending
350+
351+waitForChildren :: MonadIO m => TChan ThreadId -> IORef [EventF] -> m ()
352+waitForChildren chan pendingRef = do
353+ pending <- liftIO $ readIORef pendingRef
354+ tid <- liftIO myThreadId
355+ dbg $ "Waiting for children: " ++ show tid
356+ drainChildren chan pending
357+ dbg $ "Done: " ++ show tid
358+ liftIO $ writeIORef pendingRef []
359+
360+------------------------------------------------------------------------------
361+-- Running the monad
362+------------------------------------------------------------------------------
363+
364+-- | Run a transient computation with a default initial state
365+runTransient :: forall m a. MonadIO m => AsyncT m a -> m (Maybe a, EventF)
366+runTransient t = do
367+ zombieChan <- liftIO $ atomically newTChan
368+ pendingRef <- liftIO $ newIORef []
369+ credit <- liftIO $ newIORef maxBound
370+ th <- liftIO $ myThreadId
371+
372+ r <- runStateT (runAsyncT t) $ initEventF
373+ (empty :: AsyncT m a) th zombieChan pendingRef credit
374+
375+ waitForChildren zombieChan pendingRef
376+ return r
377+
378+-- | Run an 'AsyncT m' computation. Returns the result of the computation or
379+-- may throw an exception. The computation finishes with a 'Nothing' result
380+-- when the input sources are exhausted.
381+-- XXX pass a collector function and return a Traversable.
382+-- Ideally it should be a non-empty list instead.
383+--waitAsync :: MonadIO m => AsyncT m a -> m [a]
384+waitAsync :: MonadIO m => AsyncT m a -> m (Maybe a)
385+waitAsync m = do
386+ (r, _) <- runTransient m
387+ return r
388+
389+------------------------------------------------------------------------------
390+-- * Extensible State: Session Data Management
391+------------------------------------------------------------------------------
392+
393+-- | Retrieve a previously stored data item of the given data type from the
394+-- monad state. The data type to retrieve is implicitly determined from the
395+-- requested type context.
396+-- If the data item is not found, an 'empty' value (a void event) is returned.
397+-- Remember that an empty value stops the monad computation. If you want to
398+-- print an error message or a default value in that case, you can use an
399+-- 'Alternative' composition. For example:
400+--
401+-- > getSData <|> error "no data"
402+-- > getInt = getSData <|> return (0 :: Int)
403+getSData :: (Monad m, Typeable a) => AsyncT m a
404+getSData = AsyncT getData
405+
406+-- | 'setSData' stores a data item in the monad state which can be retrieved
407+-- later using 'getData' or 'getSData'. Stored data items are keyed by their
408+-- data type, and therefore the data type must be 'Typeable' and only one item
409+-- of a given type can be stored. A newtype wrapper can be used to distinguish
410+-- two data items of the same type when required.
411+--
412+-- @
413+-- import Control.Monad.IO.Class (liftIO)
414+-- import Transient.Base
415+-- import Data.Typeable
416+--
417+-- data Person = Person
418+-- { name :: String
419+-- , age :: Int
420+-- } deriving Typeable
421+--
422+-- main = keep $ do
423+-- setSData $ Person "Alberto" 55
424+-- Person name age <- getSData
425+-- liftIO $ print (name, age)
426+-- @
427+setSData :: (Monad m, Typeable a) => a -> AsyncT m ()
428+setSData x = AsyncT $ setData x >> return (Just ())
429+
430+-- | Accepts a function that takes the current value of the stored data type
431+-- and returns the modified value. If the function returns 'Nothing' the value
432+-- is deleted otherwise updated.
433+modifySData :: (Monad m, Typeable a) => (Maybe a -> Maybe a) -> AsyncT m ()
434+modifySData x = AsyncT $ modifyData x >> return (Just ())
435+
436+-- | Delete the data item of the given type from the monad state.
437+delSData :: (Monad m, Typeable a) => a -> AsyncT m ()
438+delSData x = AsyncT $ delData x >> return (Just ())
439+
440+------------------------------------------------------------------------------
441+-- MonadState for AsyncT m
442+------------------------------------------------------------------------------
443+
444+instance (Monad m, Monad (AsyncT m)) => MonadState EventF (AsyncT m) where
445+ get = AsyncT $ get >>= return . Just
446+ put x = AsyncT $ put x >> return (Just ())
447+ state f = AsyncT $ do
448+ s <- get
449+ let ~(a, s') = f s
450+ put s'
451+ return $ Just a
452+
453+{-
454+-- | Run an action, if the result is a void action undo any state changes
455+-- that it might have caused.
456+try :: MonadIO m => AsyncT m a -> AsyncT m a
457+try mx = do
458+ sd <- gets mfData
459+ mx <|> (modify (\s -> s { mfData = sd }) >> empty)
460+
461+-- | Executes the computation and reset the state either if it fails or not
462+sandbox :: MonadIO m => AsyncT m a -> AsyncT m a
463+sandbox mx = do
464+ sd <- gets mfData
465+ mx <*** modify (\s ->s { mfData = sd})
466+ -}
467+
468+------------------------------------------------------------------------------
469+-- More operators, instances
470+------------------------------------------------------------------------------
471+
472+instance (Num a, Monad (AsyncT m)) => Num (AsyncT m a) where
473+ fromInteger = return . fromInteger
474+ mf + mg = (+) <$> mf <*> mg
475+ mf * mg = (*) <$> mf <*> mg
476+ negate f = f >>= return . negate
477+ abs f = f >>= return . abs
478+ signum f = f >>= return . signum
479+
480+class AdditionalOperators m where
481+
482+ -- | Run @m a@ discarding its result before running @m b@.
483+ (**>) :: m a -> m b -> m b
484+
485+ -- | Run @m b@ discarding its result, after the whole task set @m a@ is
486+ -- done.
487+ (<**) :: m a -> m b -> m a
488+
489+ atEnd' :: m a -> m b -> m a
490+ atEnd' = (<**)
491+
492+ -- | Run @m b@ discarding its result, once after each task in @m a@, and
493+ -- once again after the whole task set is done.
494+ (<***) :: m a -> m b -> m a
495+
496+ atEnd :: m a -> m b -> m a
497+ atEnd = (<***)
498+
499+instance (Monad m, Monad (AsyncT m)) => AdditionalOperators (AsyncT m) where
500+
501+ (**>) :: AsyncT m a -> AsyncT m b -> AsyncT m b
502+ (**>) x y =
503+ AsyncT $ do
504+ runAsyncT x
505+ runAsyncT y
506+
507+ (<***) :: AsyncT m a -> AsyncT m b -> AsyncT m a
508+ (<***) ma mb =
509+ AsyncT $ do
510+ fs <- getContinuations
511+ setContinuation ma (\x -> mb >> return x) fs
512+ a <- runAsyncT ma
513+ runAsyncT mb
514+ restoreStack fs
515+ return a
516+
517+ (<**) :: AsyncT m a -> AsyncT m b -> AsyncT m a
518+ (<**) ma mb =
519+ AsyncT $ do
520+ a <- runAsyncT ma
521+
522+ runAsyncT mb
523+ return a
524+
525+infixr 1 <***, <**, **>
526+
527+-- | Run @b@ once, discarding its result when the first task in task set @a@
528+-- has finished. Useful to start a singleton task after the first task has been
529+-- setup.
530+(<|) :: MonadIO m => AsyncT m a -> AsyncT m b -> AsyncT m a
531+(<|) ma mb = AsyncT $ do
532+ fs <- getContinuations
533+ ref <- liftIO $ newIORef False
534+ setContinuation ma (cont ref) fs
535+ r <- runAsyncT ma
536+ restoreStack fs
537+ return r
538+ where cont ref x = AsyncT $ do
539+ n <- liftIO $ readIORef ref
540+ if n == True
541+ then return $ Just x
542+ else do liftIO $ writeIORef ref True
543+ runAsyncT mb
544+ return $ Just x
545+
src/Duct/Event.hsnew file+150
@@ -0,0 +1,150 @@
1+{-# LANGUAGE ExistentialQuantification #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+
4+module Duct.Event
5+ ( EventF(..)
6+ , initEventF
7+ , getData
8+ , setData
9+ , modifyData
10+ , delData
11+ )
12+where
13+
14+import Control.Concurrent (ThreadId)
15+import Control.Concurrent.STM (TChan)
16+import Control.Monad.State (MonadState, gets, modify)
17+import Data.Dynamic (TypeRep, Typeable, typeOf)
18+import Data.IORef (IORef)
19+import qualified Data.Map as M
20+import Unsafe.Coerce (unsafeCoerce)
21+
22+------------------------------------------------------------------------------
23+-- State of a continuation
24+------------------------------------------------------------------------------
25+
26+type SData = ()
27+
28+-- | EventF describes the context of a TransientIO computation.
29+data EventF = forall m a b. EventF
30+ { event :: Maybe SData -- untyped
31+ -- ^ event data to use in a continuation in a new thread
32+
33+ , xcomp :: m a
34+ , fcomp :: [b -> m b]
35+ -- ^ List of continuations
36+
37+ , mfData :: M.Map TypeRep SData
38+ -- ^ State data accessed with get or put operations
39+
40+ , mfSequence :: Int
41+ , threadId :: ThreadId
42+
43+ , parent :: Maybe EventF
44+ -- ^ The parent of this thread
45+
46+ -- Thread creation and cleanup handling. When a new thread is created the
47+ -- parent records it in the 'children' field and 'threadCredit' is
48+ -- decremented. When a child thread is done it sends a done event to the
49+ -- parent on an unbounded channel and goes away. Before starting a new
50+ -- thread the parent always processes the unbounded channel to clear up the
51+ -- pending zombies. This strategy ensures that the zombie queue will never
52+ -- grow more than the number of running threads. Before exiting, the
53+ -- parent thread waits on the channel until all its children are cleaned
54+ -- up.
55+
56+ , zombieChannel :: TChan ThreadId
57+ -- ^ A channel for the immediate children to communicate to when they die.
58+ -- Each thread has its own dedicated channel for its children
59+
60+ -- We always track the child threds otherwise the programmer needs to worry
61+ -- about if some threads may remain hanging due because they are stuck in
62+ -- an infinite loop or a forever blocked IO. Also, it is not clear whether
63+ -- there is a significant gain by disabling tracking. Instead of using the
64+ -- threadIds we can also keep a count instead, that will allow us to wait
65+ -- for all children to drain but we cannot kill them.
66+ --
67+ -- We need these as IORefs since we need any changes to these to be
68+ -- reflected back in the calling code that waits for the children.
69+
70+ , pendingThreads :: IORef [EventF]
71+ -- ^ Active immediate child threads of this thread, modified only by this
72+ -- thread, therefore atomic modification is not needed.
73+
74+ -- By default this limit is shared by the current tree of threads. But at
75+ -- any point the 'threads' primitive can be used to start a new limit for
76+ -- the subtree under that primitive.
77+ -- Shared across many threads, therefore atomic access is required.
78+ -- This is incremented by the child thread itelf before it exits.
79+ --
80+ , threadCredit :: IORef Int
81+ -- ^ How many more threads are allowed to be created?
82+ } deriving Typeable
83+
84+initEventF
85+ :: m a
86+ -> ThreadId
87+ -> TChan ThreadId
88+ -> IORef [EventF]
89+ -> IORef Int
90+ -> EventF
91+initEventF x th zombieChan pending credit =
92+ EventF { event = mempty
93+ , xcomp = x
94+ , fcomp = []
95+ , mfData = mempty
96+ , mfSequence = 0
97+ , threadId = th
98+ , parent = Nothing
99+ , zombieChannel = zombieChan
100+ , pendingThreads = pending
101+ , threadCredit = credit }
102+
103+------------------------------------------------------------------------------
104+-- * Extensible State: Session Data Management
105+------------------------------------------------------------------------------
106+
107+-- | Same as 'getSData' but with a more general type. If the data is found, a
108+-- 'Just' value is returned. Otherwise, a 'Nothing' value is returned.
109+getData :: (MonadState EventF m, Typeable a) => m (Maybe a)
110+getData = resp
111+ where resp = do
112+ list <- gets mfData
113+ case M.lookup (typeOf $ typeResp resp) list of
114+ Just x -> return . Just $ unsafeCoerce x
115+ Nothing -> return Nothing
116+ typeResp :: m (Maybe x) -> x
117+ typeResp = undefined
118+
119+-- | Same as 'setSData' but with a more general type.
120+setData :: (MonadState EventF m, Typeable a) => a -> m ()
121+setData x = modify $ \st -> st { mfData = M.insert t (unsafeCoerce x) (mfData st) }
122+ where t = typeOf x
123+
124+modifyData :: (MonadState EventF m, Typeable a) => (Maybe a -> Maybe a) -> m ()
125+modifyData f = modify $ \st -> st { mfData = M.alter alterf t (mfData st) }
126+ where typeResp :: (Maybe a -> b) -> a
127+ typeResp = undefined
128+ t = typeOf (typeResp f)
129+ alterf mx = unsafeCoerce $ f x'
130+ where x' = case mx of
131+ Just x -> Just $ unsafeCoerce x
132+ Nothing -> Nothing
133+
134+delData :: (MonadState EventF m, Typeable a) => a -> m ()
135+delData x = modify $ \st -> st { mfData = M.delete (typeOf x) (mfData st) }
136+
137+{-
138+getPrevId :: MonadState EventF m => m Int
139+getPrevId = gets mfSequence
140+
141+-- | Generator of identifiers that are unique within the current monadic
142+-- sequence They are not unique in the whole program.
143+genId :: MonadState EventF m => m Int
144+genId = do
145+ st <- get
146+ let n = mfSequence st
147+ put st { mfSequence = n + 1 }
148+ return n
149+
150+-}
src/Duct/Log.hsnew file+54
@@ -0,0 +1,54 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+
5+module Duct.Log
6+ ( Log(..)
7+ , LogElem(..)
8+ )
9+where
10+
11+import Control.Exception (SomeException, catch)
12+import Data.Dynamic (Typeable, typeOf)
13+import System.IO.Unsafe (unsafePerformIO)
14+
15+------------------------------------------------------------------------------
16+-- Log types
17+------------------------------------------------------------------------------
18+
19+-- | Constraint type synonym for a value that can be logged.
20+type Loggable a = (Show a, Read a, Typeable a)
21+
22+-- | Dynamic serializable data for logging.
23+data IDynamic =
24+ IDyns String
25+ | forall a. Loggable a => IDynamic a
26+
27+instance Show IDynamic where
28+ show (IDynamic x) = show (show x)
29+ show (IDyns s) = show s
30+
31+readWithErr :: (Typeable a, Read a) => String -> IO [(a, String)]
32+readWithErr line =
33+ (v `seq` return [(v, left)])
34+ `catch` (\(_ :: SomeException) ->
35+ error $ "read error trying to read type: \"" ++ show (typeOf v)
36+ ++ "\" in: " ++ " <" ++ show line ++ "> ")
37+ where [(v, left)] = readsPrec 0 line
38+
39+readsPrec' :: (Typeable a, Read a) => t -> String -> [(a, String)]
40+readsPrec' _ = unsafePerformIO . readWithErr
41+
42+instance Read IDynamic where
43+ readsPrec n str = map (\(x,s) -> (IDyns x,s)) $ readsPrec' n str
44+
45+data LogElem = Wait | Exec | Var IDynamic
46+ deriving (Read, Show)
47+
48+type Recover = Bool
49+type CurrentPointer = [LogElem]
50+type LogEntries = [LogElem]
51+
52+data Log = Log Recover CurrentPointer LogEntries
53+ deriving (Typeable, Show)
54+
src/Duct/Threads.hsnew file+394
@@ -0,0 +1,394 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+module Duct.Threads
5+ ( parallel
6+ , waitEvents
7+ , async
8+ , sample
9+ , sync
10+ , react
11+ , threads
12+ )
13+where
14+
15+import Control.Applicative ((<|>))
16+import Control.Concurrent
17+ (ThreadId, forkIO, myThreadId, threadDelay, killThread)
18+import Control.Concurrent.STM
19+ (TChan, atomically, newTChan, readTChan, tryReadTChan, writeTChan)
20+import Control.Exception (SomeException(..), ErrorCall(..), catch)
21+import qualified Control.Exception.Lifted as E
22+import Data.Dynamic (Typeable)
23+import Data.IORef
24+ (IORef, atomicModifyIORef, readIORef, writeIORef, modifyIORef,
25+ newIORef)
26+import Data.Maybe (fromJust)
27+import Control.Monad.IO.Class (MonadIO(..))
28+import Control.Monad.State
29+ (StateT, get, gets, modify, put, runStateT, when)
30+import Control.Monad.Trans.Class (MonadTrans(lift))
31+import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith)
32+import Unsafe.Coerce (unsafeCoerce)
33+
34+import Duct.AsyncT
35+import Duct.Event
36+
37+{-
38+-- | Run a transient computation with a given initial state
39+runTransState :: EventF -> AsyncT m x -> m (Maybe x, EventF)
40+runTransState st x = runStateT (runAsyncT x) st
41+
42+-- | Get the continuation context: closure, continuation, state, child threads etc
43+getCont :: Monad m => AsyncT m EventF
44+getCont = AsyncT $ Just <$> get
45+
46+-- | Run the closure and the continuation using its own state data.
47+runCont' :: (MonadIO m, Monad (AsyncT m)) => EventF -> m (Maybe a, EventF)
48+runCont' cont = runStateT (runCont cont) cont
49+
50+-- | Save a closure and continuation, run the closure, restore the old continuation.
51+-- | NOTE: The old closure is discarded.
52+withContinuation :: MonadIO m => b -> AsyncT m a -> AsyncT m a
53+withContinuation c mx = do
54+ EventF { fcomp = fs, .. } <- get
55+ put $ EventF { xcomp = unsafeCoerce mx
56+ , fcomp = unsafeCoerce c : fs
57+ , .. }
58+ r <- mx
59+ restoreStack fs
60+ return r
61+
62+-- | Run a chain of continuations.
63+-- WARNING: It is up to the programmer to assure that each continuation typechecks
64+-- with the next, and that the parameter type match the input of the first
65+-- continuation.
66+-- NOTE: Normally this makes sense to stop the current flow with `stop` after the
67+-- invocation.
68+runContinuations :: MonadIO m => [a -> AsyncT m b] -> c -> AsyncT m d
69+runContinuations fs x = compose (unsafeCoerce fs) x
70+-}
71+
72+------------------------------------------------------------------------------
73+-- Utilities
74+------------------------------------------------------------------------------
75+
76+-- | Run the closure and the continuation using the state data of the calling thread
77+runCont :: (MonadIO m, Monad (AsyncT m)) => EventF -> StateT EventF m (Maybe a)
78+runCont EventF { xcomp = x, fcomp = fs } = runAsyncT $ do
79+ r <- unsafeCoerce x
80+ compose (unsafeCoerce fs) r
81+
82+------------------------------------------------------------------------------
83+-- Thread Management (creation, reaping and killing)
84+------------------------------------------------------------------------------
85+
86+tryReclaimZombies :: MonadIO m => TChan ThreadId -> IORef [EventF] -> m ()
87+tryReclaimZombies chan pendingRef = do
88+ pending <- liftIO $ readIORef pendingRef
89+ if length pending == 0
90+ then return ()
91+ else do
92+ mtid <- liftIO $ atomically $ tryReadTChan chan
93+ case mtid of
94+ Nothing -> return ()
95+ Just tid -> do
96+ liftIO $ writeIORef pendingRef $ filter (\x -> threadId x /= tid) pending
97+ tryReclaimZombies chan pendingRef
98+
99+waitForOneChild :: MonadIO m => TChan ThreadId -> IORef [EventF] -> m ()
100+waitForOneChild chan pendingRef = do
101+ -- XXX assert pending must have at least one element
102+ -- assert that the tid is found in our list
103+ tid <- liftIO $ atomically $ readTChan chan
104+ liftIO $ modifyIORef pendingRef $ filter (\x -> threadId x /= tid)
105+
106+-- XXX this is not a real semaphore as it does not really block on wait,
107+-- instead it returns whether the value is zero or non-zero.
108+--
109+waitQSemB :: IORef Int -> IO Bool
110+waitQSemB sem = atomicModifyIORef sem $ \n ->
111+ if n > 0
112+ then (n - 1, True)
113+ else (n, False)
114+
115+signalQSemB :: IORef Int -> IO ()
116+signalQSemB sem = atomicModifyIORef sem $ \n -> (n + 1, ())
117+
118+instance Read SomeException where
119+ readsPrec _n str = [(SomeException $ ErrorCall s, r)]
120+ where [(s , r)] = read str
121+
122+-- Allocation of threads
123+--
124+-- global thread limit
125+-- thread fan-out i.e. per thread children limit
126+-- min per thread allocation to avoid starvation
127+--
128+-- dynamic adjustment based on the cost, speed of consumption, cpu utilization
129+-- etc. We need to adjust the limits based on cost, throughput and latencies.
130+--
131+-- The event producer thread must put the work on a work-queue and the child
132+-- threads can pick it up from there. But if there is just one consumer then it
133+-- may not make sense to have a separate producer unless the producing cost is
134+-- high.
135+--
136+
137+forkFinally1 :: (MonadIO m, MonadBaseControl IO m) =>
138+ m a -> (Either SomeException a -> IO ()) -> m ThreadId
139+forkFinally1 action and_then =
140+ E.mask $ \restore ->
141+ liftBaseWith $ \runInIO -> forkIO $ do
142+ runInIO $ E.try (restore action) >>= liftIO . and_then
143+ return ()
144+
145+forkIt :: (MonadBaseControl IO m, MonadIO m) => EventF -> (EventF -> m t) -> m ()
146+forkIt current proc = do
147+ child <- newChildCont current
148+ tid <- forkFinally1 (runInChild proc child)
149+ $ \res -> do
150+ thId <- myThreadId
151+ beforeExit child{threadId = thId} res
152+ updatePendingThreads current child{threadId = tid}
153+ return ()
154+
155+ where
156+
157+ updatePendingThreads :: MonadIO m => EventF -> EventF -> m ()
158+ updatePendingThreads cur child = do
159+ -- update the new thread first so that if it exited already reclaim finds
160+ -- it in the list and does not panic.
161+ liftIO $ modifyIORef (pendingThreads cur) $ (\ts -> child:ts)
162+ tryReclaimZombies (zombieChannel cur) (pendingThreads cur)
163+
164+ newChildCont cur = do
165+ pendingRef <- liftIO $ newIORef []
166+ chan <- liftIO $ atomically newTChan
167+ -- shares the threadCredit of the parent by default
168+ return $ cur
169+ { parent = Just cur
170+ , pendingThreads = pendingRef
171+ , zombieChannel = chan
172+ }
173+
174+ runInChild m child = do
175+ th <- liftIO $ myThreadId
176+ dbg $ "Forked: child " ++ show th
177+ m child{threadId = th}
178+
179+ beforeExit cur res = do
180+ case res of
181+ Left _exc -> -- kill all child threads
182+ -- propagate the exception to the parent
183+ return ()
184+ Right _ -> -- collect the results?
185+ return ()
186+
187+ waitForChildren (zombieChannel cur) (pendingThreads cur)
188+ signalQSemB (threadCredit cur)
189+ let p = fromJust (parent cur)
190+ liftIO $ atomically $ writeTChan (zombieChannel p) (threadId cur)
191+
192+forkMaybe :: (MonadBaseControl IO m, MonadIO m) => EventF -> (EventF -> m ()) -> m ()
193+forkMaybe current toRun = do
194+ gotCredit <- liftIO $ waitQSemB (threadCredit current)
195+ pending <- liftIO $ readIORef $ pendingThreads current
196+ case gotCredit of
197+ False -> case pending of
198+ [] -> toRun current -- run synchronously
199+ _ -> do
200+ -- XXX If we have unreclaimable child threads e.g.
201+ -- infinite loop, this is going to deadlock us. We need
202+ -- special handling for those cases. Add those to
203+ -- unreclaimable list? And always execute them in an
204+ -- async thread, cannot use sync for those.
205+ waitForOneChild (zombieChannel current)
206+ (pendingThreads current)
207+ forkMaybe current toRun
208+ True -> do
209+ dbg $ "Forking: parent " ++ show (threadId current)
210+ forkIt current toRun
211+
212+-- | 'StreamData' represents a task in a task stream being generated.
213+data StreamData a =
214+ SMore a -- ^ More tasks to come
215+ | SLast a -- ^ This is the last task
216+ | SDone -- ^ No more tasks, we are done
217+ | SError SomeException -- ^ An error occurred
218+ deriving (Typeable, Show,Read)
219+
220+-- | Execute the IO action and the continuation
221+genAsyncEvents :: (MonadIO m, MonadBaseControl IO m) => EventF -> IO (StreamData t) -> m ()
222+genAsyncEvents parentc rec =
223+ forkMaybe parentc $ \childcont -> loop childcont >> return ()
224+
225+ where
226+
227+ -- Execute the IO computation and then the closure-continuation
228+ loop curcont = do
229+ streamData <- liftIO $ rec `catch`
230+ \(e :: SomeException) -> return $ SError e
231+
232+ let run = runContWith streamData
233+ case streamData of
234+ SMore _ -> do
235+ forkMaybe curcont run
236+ loop curcont
237+ _ -> run curcont
238+
239+ where
240+
241+ -- pass on the io result and then run the continuation
242+ runContWith dat cont = do
243+ let cont' = cont { event = Just $ unsafeCoerce dat }
244+ _ <- runStateT (runCont cont') cont'
245+ return ()
246+
247+-- | Run an IO action one or more times to generate a stream of tasks. The IO
248+-- action returns a 'StreamData'. When it returns an 'SMore' or 'SLast' a new
249+-- task is triggered with the result value. If the return value is 'SMore', the
250+-- action is run again to generate the next task, otherwise task creation
251+-- stops.
252+--
253+-- Unless the maximum number of threads (set with 'threads') has been reached,
254+-- the task is generated in a new thread and the current thread returns a void
255+-- task.
256+parallel :: (Monad m, MonadIO m, MonadBaseControl IO m)
257+ => IO (StreamData a) -> AsyncT m (StreamData a)
258+parallel ioaction = AsyncT $ do
259+ cont <- get
260+ case event cont of
261+ -- we have already exceuted the ioaction and now we are continuing in a new
262+ -- thread. Just return the result of the ioaction stored in 'event' field.
263+ j@(Just _) -> do
264+ put cont { event = Nothing }
265+ return $ unsafeCoerce j
266+ -- we have to execute the io action and generate an event and then continue
267+ -- in this thread or a new thread.
268+ Nothing -> do
269+ lift $ genAsyncEvents cont ioaction
270+ was <- getData `onNothing` return NoRemote
271+ when (was /= WasRemote) $ setData WasParallel
272+ return Nothing
273+
274+-- | An task stream generator that produces an infinite stream of tasks by
275+-- running an IO computation in a loop. A task is triggered carrying the output
276+-- of the computation. See 'parallel' for notes on the return value.
277+waitEvents :: (MonadIO m, MonadBaseControl IO m) => IO a -> AsyncT m a
278+waitEvents io = do
279+ mr <- parallel (SMore <$> io)
280+ case mr of
281+ SMore x -> return x
282+ SError e -> back e
283+
284+-- | Run an IO computation asynchronously and generate a single task carrying
285+-- the result of the computation when it completes. See 'parallel' for notes on
286+-- the return value.
287+async :: (MonadIO m, MonadBaseControl IO m) => IO a -> AsyncT m a
288+async io = do
289+ mr <- parallel (SLast <$> io)
290+ case mr of
291+ SLast x -> return x
292+ SError e -> back e
293+
294+-- | Force an async computation to run synchronously. It can be useful in an
295+-- 'Alternative' composition to run the alternative only after finishing a
296+-- computation. Note that in Applicatives it might result in an undesired
297+-- serialization.
298+sync :: MonadIO m => AsyncT m a -> AsyncT m a
299+sync x = do
300+ setData WasRemote
301+ r <- x
302+ delData WasRemote
303+ return r
304+
305+-- | An task stream generator that produces an infinite stream of tasks by
306+-- running an IO computation periodically at the specified time interval. The
307+-- task carries the result of the computation. A new task is generated only if
308+-- the output of the computation is different from the previous one. See
309+-- 'parallel' for notes on the return value.
310+sample :: (Eq a, MonadIO m, MonadBaseControl IO m) => IO a -> Int -> AsyncT m a
311+sample action interval = do
312+ v <- liftIO action
313+ prev <- liftIO $ newIORef v
314+ waitEvents (loop action prev) <|> async (return v)
315+ where loop act prev = loop'
316+ where loop' = do
317+ threadDelay interval
318+ v <- act
319+ v' <- readIORef prev
320+ if v /= v' then writeIORef prev v >> return v else loop'
321+
322+-- | Make a transient task generator from an asynchronous callback handler.
323+--
324+-- The first parameter is a callback. The second parameter is a value to be
325+-- returned to the callback; if the callback expects no return value it
326+-- can just be a @return ()@. The callback expects a setter function taking the
327+-- @eventdata@ as an argument and returning a value to the callback; this
328+-- function is supplied by 'react'.
329+--
330+-- Callbacks from foreign code can be wrapped into such a handler and hooked
331+-- into the transient monad using 'react'. Every time the callback is called it
332+-- generates a new task for the transient monad.
333+--
334+react
335+ :: (Monad m, MonadIO m)
336+ => ((eventdata -> m response) -> m ())
337+ -> IO response
338+ -> AsyncT m eventdata
339+react setHandler iob = AsyncT $ do
340+ cont <- get
341+ case event cont of
342+ Nothing -> do
343+ lift $ setHandler $ \dat ->do
344+ runStateT (runCont cont) cont{event= Just $ unsafeCoerce dat}
345+ liftIO iob
346+ was <- getData `onNothing` return NoRemote
347+ when (was /= WasRemote) $ setData WasParallel
348+ return Nothing
349+
350+ j@(Just _) -> do
351+ put cont{event=Nothing}
352+ return $ unsafeCoerce j
353+
354+-- Need to propagate the exception via kill exception handler
355+-- | kill all the child threads associated with the continuation context
356+killChildren :: IORef [EventF] -> IO ()
357+killChildren pendingRef = do
358+ ths <- readIORef pendingRef
359+ mapM_ (killThread . threadId) ths
360+
361+------------------------------------------------------------------------------
362+-- Controlling thread quota
363+------------------------------------------------------------------------------
364+
365+-- XXX Should n be Word32 instead?
366+-- | Runs a computation under a given thread limit. A limit of 0 means new
367+-- tasks start synchronously in the current thread. New threads are created by
368+-- 'parallel', and APIs that use parallel.
369+threads :: MonadIO m => Int -> AsyncT m a -> AsyncT m a
370+threads n process = do
371+ oldCr <- gets threadCredit
372+ newCr <- liftIO $ newIORef n
373+ modify $ \s -> s { threadCredit = newCr }
374+ r <- process
375+ <** (modify $ \s -> s { threadCredit = oldCr }) -- restore old credit
376+ return r
377+
378+{-
379+-- | Run a "non transient" computation within the underlying state monad, so it
380+-- is guaranteed that the computation neither can stop nor can trigger
381+-- additional events/threads.
382+noTrans :: Monad m => StateM m x -> AsyncT m x
383+noTrans x = AsyncT $ x >>= return . Just
384+
385+-- This can be used to set, increase or decrease the existing limit. The limit
386+-- is shared by multiple threads and therefore needs to modified atomically.
387+-- Note that when there is no limit the limit is set to maxBound it can
388+-- overflow with an increment and get reduced instead of increasing.
389+-- XXX should we use a Maybe instead? Or use separate inc/dec/set APIs to
390+-- handle overflow properly?
391+--
392+-- modifyThreads :: MonadIO m => (Int -> Int) -> AsyncT m ()
393+-- modifyThreads f =
394+-}
stack.yamlnew file+7
@@ -0,0 +1,7 @@
1+resolver: lts-7.7
2+packages:
3+- '.'
4+extra-deps: []
5+flags: {}
6+extra-package-dbs: []
7+pvp-bounds: both