packages feed

Control-Engine 0.0.6 → 1.0.0.0

raw patch · 2 files changed

+78/−42 lines, 2 filesdep +BoundedChandep +concurrentdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: BoundedChan, concurrent

Dependency ranges changed: base

API changes (from Hackage documentation)

- Control.Engine: Eng :: Chan job -> Chan result -> TVar [Hook state job] -> TVar [Hook state job] -> TVar [Hook state result] -> TVar [Hook state result] -> TVar state -> Engine job result state
+ Control.Engine: Eng :: BoundedChan job -> BoundedChan result -> TVar [Hook state job] -> TVar [Hook state job] -> TVar [Hook state result] -> TVar [Hook state result] -> TVar state -> Engine job result state
- Control.Engine: chan1 :: Engine job result state -> Chan job
+ Control.Engine: chan1 :: Engine job result state -> BoundedChan job
- Control.Engine: chan2 :: Engine job result state -> Chan result
+ Control.Engine: chan2 :: Engine job result state -> BoundedChan result
- Control.Engine: initEngine :: (Eq st) => Int -> (IO job) -> (result -> IO ()) -> (st -> job -> IO (Maybe result)) -> st -> IO (Engine job result st)
+ Control.Engine: initEngine :: (Eq st) => Int -> Int -> (IO job) -> (result -> IO ()) -> (st -> job -> IO (Maybe result)) -> st -> IO (Engine job result st)
- Control.Engine: initSimpleEngine :: Int -> (job -> result) -> IO (Chan job, Chan result)
+ Control.Engine: initSimpleEngine :: Int -> (job -> result) -> IO (BoundedChan job, BoundedChan result)
- Control.Engine: initSimpleEngineIO :: Int -> (job -> IO result) -> IO (Chan job, Chan result)
+ Control.Engine: initSimpleEngineIO :: Int -> (job -> IO result) -> IO (BoundedChan job, BoundedChan result)

Files

Control-Engine.cabal view
@@ -1,5 +1,5 @@ name:		Control-Engine-version:	0.0.6+version:	1.0.0.0 license:	BSD3 license-file:	LICENSE author:		Thomas DuBuisson <thomas.dubuisson@gmail.com>@@ -12,8 +12,8 @@ category:	Control stability:	stable build-type:	Simple-cabal-version:	>= 1.2-tested-with:	GHC == 6.8.3+cabal-version:	>= 1.6+tested-with:	GHC == 6.10.3 extra-source-files:  Flag small_base@@ -21,9 +21,9 @@  Library   if flag(small_base)-    Build-Depends: base >= 3, stm+    Build-Depends: base == 4.*, stm, concurrent, BoundedChan >= 1.0.0.2   else-    Build-Depends: base >= 3, stm+    Build-Depends: base == 4.*, stm, BoundedChan >= 1.0.0.2   hs-source-dirs:   exposed-modules: Control.Engine, Control.ThreadPool   ghc-options:
Control/Engine.hs view
@@ -1,56 +1,74 @@-{-| Implemented here is a thread pool library on crack.- +{-|   /1.0 Introduction/    Typically, a thread pool is a set of execution contexts that will execute-  tasks from an input queue.  Typically, thread pools are used to parallize+  tasks from an input queue.  Thread pools are used to parallize   the processing of incoming work across all available CPUs without going   through the expense of starting a new thread for every new task.     In 'Control.Engine' you will find a somewhat unique implementation.  The   'Engine' is not only a set of threads running a common mutator on the input-  queue, producing an output queue, but also include hooks, task injection, and-  state management.+  queue, placing results on an output queue, but also include hooks, task+  injection, and state management. + @+  +--------+  chan1 +------------------------+ chan2  +--------++  | In Hk  +  --->  | PreMH, Mutator, PostMH | -----> | Out Hk |+  +--------+        +------------------------+        +--------++       ^                 ^                             ^+       |                 |                             |+       |                 |    IO Ref                   |+       +-----------------+-----------------------------++                         |+                   +------------++                   | State TVar |+                   +------------++ @ -  /Queues :: (Chan a)/ - from "Control.Concurrent.Chan".+  /Queues :: (BoundedChan a)/ - from "Control.Concurrent.BoundedChan". +  The system uses two primary queues.  One for transporting data from+  pre-mutator hooks to the mutator.  One for data from the mutator to the+  post-mutator hooks.  These channels are size-bounded - which is needed+  mostly due to the inflexibility of the scheduler.    /Hooks :: (a -> IO Maybe a)/    Hooks can be added and removed during execution without creating a new   engine. They allow the developer to modify tasks: -   * prior to parallization (for sequential preprocessing)+   * Input hooks - prior to parallization (for sequential preprocessing) -   * in parallel, prior to main mutation funciton+   * Pre-Mutator hooks - in parallel, prior to main mutation funciton -   * in parallel, after mutation function+   * Post-Mutator hooks - in parallel, after mutation function -   * post parallization (for sequential post processing)+   * Output hooks - post parallization (for sequential post processing)       /State Management/ +  Control-Engine manages state for you.  Semantically, all workers and hooks+  will see a correct state but it won't always be the most recent or consistent+  between threads.+   The stateManager waits for any updates to the mutator state or hooks.  If any-  modifications are made then the new set of hooks (or state) is provided+  modifications are made then the new set of hooks or state is provided   to the workers.  Correctness is handled by keeping the master copies as TVars-  ("Control.Concurrent.STM").  While the mutators and hooks read from an 'MVar'-  ("Control.Concurrent.MVar") to avoid contention.+  ("Control.Concurrent.STM").  While the mutators and hooks read state from an+  'IORef' ("Control.Concurrent.IORef") to avoid contention.    The thinking here is that changing the hooks and state is a rare / low   contention action while the need for this information will be constant   and performance critical.  How successful this stratagy is has yet to   be shown.--     /Injection/ -  One injection point allows injection of a result that had no preceding-  task.  The second injector allows the initial hooks ('Input' hooks) to be-  bypassed.+  One injection point allows injection of a result that had no preceding task -+  thus the result is only seen by the output hooks. Another injector allows the+  input hooks to be bypassed.  -}  module Control.Engine@@ -75,17 +93,17 @@ 	, injectPostMutator 	) where -import Control.Concurrent+import Control.Concurrent (forkIO) import Control.Concurrent.STM-import Control.Concurrent.Chan+import Control.Concurrent.BoundedChan import Control.Monad import Data.IORef import Data.List (insert)  -- |An 'Engine' represents a pool of threads ready to execute tasks. data Engine job result state =-    Eng { chan1			:: Chan job-	, chan2			:: Chan result+    Eng { chan1			:: BoundedChan job+	, chan2			:: BoundedChan result 	, tvInHook		:: TVar [Hook state job] 	, tvPreMutateHook	:: TVar [Hook state job] 	, tvPostMutateHook	:: TVar [Hook state result]@@ -106,28 +124,39 @@ -- You should consider using "Control.ThreadPool" instead. -- -- Evaluation of the result is forced using seq.-initSimpleEngine :: Int -> (job -> result) -> IO (Chan job, Chan result)+-- Input, output, and intermediate channels are length bounded to a multiple+-- of the number of workers.+initSimpleEngine :: Int -> (job -> result) -> IO (BoundedChan job, BoundedChan result) initSimpleEngine nr mutator = do-	input <- newChan-	output <- newChan+	input <- newBoundedChan chanBound+	output <- newBoundedChan chanBound 	let m = const (return . Just . mutator)-	initEngine nr (readChan input) (\o -> o `seq` writeChan output o) m ()+	initEngine nr chanBound (readChan input) (\o -> o `seq` writeChan output o) m () 	return (input, output)+  where+  chanBound = nr * 8  -- |Simpler than calling 'initEngine', but it allows no state or interaction -- with the hooks and injectors. No strictness is forced.-initSimpleEngineIO :: Int -> (job -> IO result) -> IO (Chan job, Chan result)+--+-- Input, output, and intermediate channels are length bounded to a multiple+-- of the number of workers.+initSimpleEngineIO :: Int -> (job -> IO result) -> IO (BoundedChan job, BoundedChan result) initSimpleEngineIO nr mutator = do-	input <- newChan-	output <- newChan+	input <- newBoundedChan chanBound+	output <- newBoundedChan chanBound 	let m = (\_ j -> mutator j >>= return . Just)-	initEngine nr (readChan input) (writeChan output) m ()+	initEngine nr chanBound (readChan input) (writeChan output) m () 	return (input, output)+  where+  chanBound = nr * 8  -- |To initilize an engine you must provide: -- --    * the number of threads --+--    * the maxiumum channel size for intermediate channels+-- --    * an action that will get the input -- --    * an action that will consume output@@ -138,10 +167,17 @@ -- -- No strictness is forced - be sure you force evaluation if wanted. -- All hooks start out empty.-initEngine :: (Eq st) => Int -> (IO job) -> (result -> IO ()) -> (st -> job -> IO (Maybe result)) -> st -> IO (Engine job result st)-initEngine nrWorkers input output mutator initialState = do-	c1 <- newChan-	c2 <- newChan+initEngine :: (Eq st) => +        Int ->+        Int ->+        (IO job) ->+        (result -> IO ()) ->+        (st -> job -> IO (Maybe result)) ->+        st ->+        IO (Engine job result st)+initEngine nrWorkers chanBound input output mutator initialState = do+	c1 <- newBoundedChan chanBound+	c2 <- newBoundedChan chanBound  	inputHooks       <- newIORef [] 	outputHooks      <- newIORef []@@ -164,7 +200,7 @@ 	forM_ [1..nrWorkers] $ \_ -> forkIO (worker c1 eref mutator c2) 	return engine -worker :: Chan job -> RefEngine job result st -> (st -> job -> IO (Maybe result)) -> Chan result -> IO ()+worker :: BoundedChan job -> RefEngine job result st -> (st -> job -> IO (Maybe result)) -> BoundedChan result -> IO () worker c1 eref mutator c2 = forever $ readChan c1 >>= worker'   where   worker' msg = do@@ -207,7 +243,7 @@ 	updateState new  -- Input.hs-inputManager :: (IO job) -> Chan job -> RefEngine job result st -> IO ()+inputManager :: (IO job) -> BoundedChan job -> RefEngine job result st -> IO () inputManager input outChan eref = forever $ input >>= handleMsg   where   handleMsg msg = do@@ -219,7 +255,7 @@ 		Nothing -> return ()  -- Output.hs-outputManager :: (result -> IO ()) -> Chan result -> RefEngine job result state -> IO ()+outputManager :: (result -> IO ()) -> BoundedChan result -> RefEngine job result state -> IO () outputManager output msgChan eref = forever $ do 	m <- readChan msgChan 	hook <- readIORef (refOutHook eref)