packages feed

scheduler 1.1.0 → 1.2.0

raw patch · 5 files changed

+83/−33 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Control.Scheduler: scheduleWorkId :: Scheduler m a -> (Int -> m a) -> m ()
+ Control.Scheduler: scheduleWorkId_ :: Scheduler m () -> (Int -> m ()) -> m ()

Files

+ CHANGELOG.md view
@@ -0,0 +1,15 @@+# 1.2.0++* Addition of `scheduleWorkId` and `scheduleWorkId_`++# 1.1.0++* Add functions: `replicateConcurrently` and `replicateConcurrently_`+* Made `traverseConcurrently_` lazy, thus making it possible to apply to infinite lists and other such+  foldables.+* Fix `Monoid` instance for `Comp`+* Addition of `Par'` pattern++# 1.0.0++Initial release.
README.md view
@@ -71,6 +71,22 @@ [11,22,33,44,55] ``` +### Identify workers doing the work++Since version `scheduler-1.2.0` it is possible to identify which worker is doing the+job. This is especially useful for limiting resources to particular workers that should+not be shared between separate threads.++```haskell+λ> let scheduleId = (`scheduleWorkId` (\ i -> threadDelay 100000 >> pure i))+λ> withScheduler (ParOn [4,7,5]) $ \s -> scheduleId s >> scheduleId s >> scheduleId s+[4,7,5]+λ> withScheduler (ParN 3) $ \s -> scheduleId s >> scheduleId s >> scheduleId s+[1,2,0]+λ> withScheduler (ParN 3) $ \s -> scheduleId s >> scheduleId s >> scheduleId s+[0,1,2]+```+ ### Exceptions  Whenever any of the scheduled jobs result in an exception, all of the workers will be killed and the
scheduler.cabal view
@@ -1,5 +1,5 @@ name:                scheduler-version:             1.1.0+version:             1.2.0 synopsis:            Work stealing scheduler. description:         A work stealing scheduler that is primarly developed for [massiv](https://github.com/lehins/massiv) array librarry, but it is general enough to be useful for any computation that fits the model of few workers many jobs. homepage:            https://github.com/lehins/haskell-scheduler@@ -11,6 +11,7 @@ category:            Parallelism, Concurrency build-type:          Custom extra-source-files:  README.md+                   , CHANGELOG.md cabal-version:       >=1.10  custom-setup
src/Control/Scheduler.hs view
@@ -19,6 +19,8 @@   , numWorkers   , scheduleWork   , scheduleWork_+  , scheduleWorkId+  , scheduleWorkId_   , terminate   , terminate_   , terminateWith@@ -48,8 +50,8 @@ import Data.Atomics (atomicModifyIORefCAS, atomicModifyIORefCAS_) import qualified Data.Foldable as F (foldl', traverse_) import Data.IORef-import Data.Traversable import Data.Maybe (catMaybes)+import Data.Traversable   data Jobs m a = Jobs@@ -63,16 +65,17 @@ -- -- @since 1.0.0 data Scheduler m a = Scheduler-  { numWorkers    :: {-# UNPACK #-} !Int+  { numWorkers     :: {-# UNPACK #-} !Int   -- ^ Get the number of workers. Will mainly depend on the computation strategy and/or number of   -- capabilities you have. Related function is `getCompWorkers`.   --   -- @since 1.0.0-  , scheduleWork  :: m a -> m ()-  -- ^ Schedule an action to be picked up and computed by a worker from a pool of jobs.+  , scheduleWorkId :: (Int -> m a) -> m ()+  -- ^ Schedule an action to be picked up and computed by a worker from a pool of+  -- jobs. Argument supplied to the job will be the id of the worker doing the job.   ---  -- @since 1.0.0-  , terminate     :: a -> m a+  -- @since 1.2.0+  , terminate      :: a -> m a   -- ^ As soon as possible try to terminate any computation that is being performed by all workers   -- managed by this scheduler and collect whatever results have been computed, with supplied   -- element guaranteed to being the last one.@@ -81,7 +84,7 @@   -- although it will make sure their results are discarded.   --   -- @since 1.1.0-  , terminateWith :: a -> m a+  , terminateWith  :: a -> m a   -- ^ Same as `terminate`, but returning a single element list containing the supplied   -- argument. This can be very useful for parallel search algorithms.   --@@ -92,12 +95,26 @@   -- @since 1.1.0   } --- | Same as `scheduleWork`, but only for a `Scheduler` that doesn't keep the result.++-- | Schedule an action to be picked up and computed by a worker from a pool of+-- jobs. Similar to `scheduleWorkId`, except the job doesn't get the worker id. --+-- @since 1.0.0+scheduleWork :: Scheduler m a -> m a -> m ()+scheduleWork scheduler f = scheduleWorkId scheduler (const f)++-- | Same as `scheduleWork`, but only for a `Scheduler` that doesn't keep the results.+-- -- @since 1.1.0 scheduleWork_ :: Scheduler m () -> m () -> m () scheduleWork_ = scheduleWork +-- | Same as `scheduleWorkId`, but only for a `Scheduler` that doesn't keep the results.+--+-- @since 1.2.0+scheduleWorkId_ :: Scheduler m () -> (Int -> m ()) -> m ()+scheduleWorkId_ = scheduleWorkId+ -- | Similar to `terminate`, but for a `Scheduler` that does not keep any results of computation. -- -- /Important/ - In case of `Seq` computation strategy this function has no affect.@@ -113,7 +130,7 @@ trivialScheduler_ :: Applicative f => Scheduler f () trivialScheduler_ = Scheduler   { numWorkers = 1-  , scheduleWork = id+  , scheduleWorkId = \f -> f 0   , terminate = const $ pure ()   , terminateWith = const $ pure ()   }@@ -172,21 +189,21 @@   withScheduler_ comp $ \s -> scheduleWork s $ replicateM_ n (scheduleWork s $ void f)  -scheduleJobs :: MonadIO m => Jobs m a -> m a -> m ()+scheduleJobs :: MonadIO m => Jobs m a -> (Int -> m a) -> m () scheduleJobs = scheduleJobsWith mkJob  -- | Similarly to `scheduleWork`, but ignores the result of computation, thus having less overhead. -- -- @since 1.0.0-scheduleJobs_ :: MonadIO m => Jobs m a -> m b -> m ()-scheduleJobs_ = scheduleJobsWith (return . Job_ . void)+scheduleJobs_ :: MonadIO m => Jobs m a -> (Int -> m b) -> m ()+scheduleJobs_ = scheduleJobsWith (\job -> pure (Job_ (void . job))) -scheduleJobsWith :: MonadIO m => (m b -> m (Job m a)) -> Jobs m a -> m b -> m ()+scheduleJobsWith :: MonadIO m => ((Int -> m b) -> m (Job m a)) -> Jobs m a -> (Int -> m b) -> m () scheduleJobsWith mkJob' jobs action = do   liftIO $ atomicModifyIORefCAS_ (jobsCountRef jobs) (+ 1)   job <--    mkJob' $ do-      res <- action+    mkJob' $ \ i -> do+      res <- action i       res `seq`         dropCounterOnZero (jobsCountRef jobs) $         retireWorkersN (jobsQueue jobs) (jobsNumWorkers jobs)@@ -213,14 +230,15 @@ -- | Runs the worker until the job queue is exhausted, at which point it will execute the final task -- of retirement and return runWorker :: MonadIO m =>-             JQueue m a+             Int+          -> JQueue m a           -> m () -- ^ Action to run upon retirement           -> m ()-runWorker jQueue onRetire = go+runWorker wId jQueue onRetire = go   where     go =       popJQueue jQueue >>= \case-        Just job -> job >> go+        Just job -> job wId >> go         Nothing -> onRetire  @@ -275,7 +293,7 @@ withSchedulerInternal ::      MonadUnliftIO m   => Comp -- ^ Computation strategy-  -> (Jobs m a -> m a -> m ()) -- ^ How to schedule work+  -> (Jobs m a -> (Int -> m a) -> m ()) -- ^ How to schedule work   -> (JQueue m a -> m [Maybe a]) -- ^ How to collect results   -> ([a] -> [a]) -- ^ Adjust results in some way   -> (Scheduler m a -> m b)@@ -291,7 +309,7 @@       scheduler =         Scheduler           { numWorkers = jobsNumWorkers-          , scheduleWork = submitWork jobs+          , scheduleWorkId = submitWork jobs           , terminate =               \a -> do                 mas <- collect jobsQueue@@ -311,24 +329,24 @@   _ <- onScheduler scheduler   -- / Ensure at least something gets scheduled, so retirement can be triggered   jc <- liftIO $ readIORef jobsCountRef-  when (jc == 0) $ scheduleJobs_ jobs (pure ())+  when (jc == 0) $ scheduleJobs_ jobs (\_ -> pure ())   let spawnWorkersWith fork ws =         withRunInIO $ \run ->           forM ws $ \w ->             fork w $ \unmask ->               catch-                (unmask $ run $ runWorker jobsQueue onRetire)+                (unmask $ run $ runWorker w jobsQueue onRetire)                 (run . handleWorkerException jobsQueue workDoneMVar jobsNumWorkers)       spawnWorkers =         case comp of           Seq -> return []             -- \ no need to fork threads for a sequential computation-          Par -> spawnWorkersWith forkOnWithUnmask [1 .. jobsNumWorkers]+          Par -> spawnWorkersWith forkOnWithUnmask [0 .. jobsNumWorkers - 1]           ParOn ws -> spawnWorkersWith forkOnWithUnmask ws-          ParN _ -> spawnWorkersWith (\_ -> forkIOWithUnmask) [1 .. jobsNumWorkers]+          ParN _ -> spawnWorkersWith (\_ -> forkIOWithUnmask) [0 .. jobsNumWorkers - 1]       terminateWorkers = liftIO . traverse_ (`throwTo` SomeAsyncException WorkerTerminateException)       doWork tids = do-        when (comp == Seq) $ runWorker jobsQueue onRetire+        when (comp == Seq) $ runWorker 0 jobsQueue onRetire         mExc <- liftIO $ readMVar workDoneMVar         -- \ wait for all worker to finish. If any one of them had a problem this MVar will         -- contain an exception
src/Control/Scheduler/Queue.hs view
@@ -43,17 +43,17 @@         y:ys -> Just (y, queue {qQueue = ys, qStack = []})  data Job m a-  = Job !(IORef (Maybe a)) !(m a)-  | Job_ !(m ())+  = Job !(IORef (Maybe a)) (Int -> m a)+  | Job_ (Int -> m ())   | Retire  -mkJob :: MonadIO m => m a -> m (Job m a)+mkJob :: MonadIO m => (Int -> m a) -> m (Job m a) mkJob action = do   resRef <- liftIO $ newIORef Nothing   return $!-    Job resRef $ do-      res <- action+    Job resRef $ \ i -> do+      res <- action i       liftIO $ writeIORef resRef $ Just res       return $! res @@ -85,7 +85,7 @@            , liftIO $ putMVar qBaton ()))  -popJQueue :: MonadIO m => JQueue m a -> m (Maybe (m ()))+popJQueue :: MonadIO m => JQueue m a -> m (Maybe (Int -> m ())) popJQueue (JQueue jQueueRef) = liftIO inner   where     inner =@@ -96,7 +96,7 @@           Just (job, newQueue) ->             ( newQueue             , case job of-                Job _ action -> return $ Just (void action)+                Job _ action -> return $ Just (void . action)                 Job_ action_ -> return $ Just action_                 Retire       -> return Nothing)