-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Task prioritization. -- -- Implements cooperative task prioritization with room synchronization. -- -- In the simplest usage, for an unprioritized FILO queue, only three -- operations are needed: simpleTaskPool, claim, and -- startQueue. -- --
--   (pool :: TaskPool () ()) <- simpleTaskPool
--   forkIO $ claim Acquire pool $ putStrLn Hello world!
--   forkIO $ claim Acquire pool $ putStrLn Goodbye world!
--   startQueue pool
--   
-- -- For a simple prioritized queue, the schedule operation -- introduces the priority. Prioritization is always least-first. -- --
--   prio_pool <- simpleTaskPool
--   forkIO $ claim Acquire (schedule prio_pool 1) $ putStrLn Hello world!
--   forkIO $ claim Acquire (schedule prio_pool 2) $ putStrLn Goodbye world!
--   startQueue prio_pool
--   
-- -- Note that if you run these programs with +RTS -N2 or greater, -- the claim operations may be processed in any order, since -- simpleTaskQueue detects the number of capabilities and -- schedules tasks on each. -- -- TaskPools are not thread pools. The concept is similar to IO -- Completion Ports. There are no worker threads. If a number of threads -- are waiting, the thread that is most likely to be processed next is -- woken and temporarily serves as a working thread. TaskPools are -- backed by carefully written STM (software transactional memory) -- transactions. -- -- A salient feature is that, because any thread can participate, a -- TaskPool supports both bound threads and threads created with -- forkOnIO. -- -- For applications that have complex resource constraints, it is -- possible to create a Room to model each constraint. -- Rooms are fully reentrant, and an arbitrary number of threads -- can claim a Room according to arbitrary rules, or -- RoomConstraints. In the simple usage above, a single room -- represents the number of capabilities available to the GHC runtime. -- -- Whenever a thread attempts to acquire a Room, a Claim is -- generated that represents that attempt. The Claim can be -- approved immediately, or it can be approved at the whim of another -- thread that has access to that Claim. This means that -- Rooms can be constructed in such a way that Claims are -- approved only when a third party thread sees that the resource -- constraint modeled by that Room has been satisfied. -- -- The rules for generating and approving Claims are described by -- a RoomContext. By default, Claims are approved -- immediately if their associated RoomConstraints have been -- satisfied, but when a TaskPool is introduced approval is -- deferred for prioritization. -- -- Room constraints are completely advisory: any task may claim -- any Room without restriction at any time by using the -- UnconstrainedRoomContext. -- -- Queues are used to prioritize tasks. Even if you have no need -- for prioritization, a Queue ensures that only one thread is -- woken up when a Room becomes available. A Queue -- systematically examines to a configurable depth all waiting threads -- with their priorities and constraints and wakes the most eagerly -- prioritized thread whose constraints can be satisfied. -- -- A TaskPool combines Rooms and Queues in an -- efficient, easy-to-use interface. -- -- The git repository is available at -- http://www.downstairspeople.org/git/priority-sync.git. @package priority-sync @version 0.1.0.0 module Control.Concurrent.Priority.RoomConstraint class RoomConstraint u approveConstraint :: (RoomConstraint u) => Claim a -> u -> STM () -- | A maximum limit on the number of threads allowed to claim a room. newtype MaxThreads MaxThreads :: Int -> MaxThreads -- | approve some claims according to their constraints. approveClaims :: (RoomConstraint u) => [Claim u] -> STM () instance (RoomConstraint a) => RoomConstraint (Maybe a) instance (RoomConstraint a, RoomConstraint b) => RoomConstraint (Either a b) instance (RoomConstraint a, RoomConstraint b) => RoomConstraint (a, b) instance (RoomConstraint u) => RoomConstraint (STM u) instance RoomConstraint MaxThreads instance RoomConstraint Bool instance RoomConstraint () module Control.Concurrent.Priority.Queue -- | A prioritized Queue. Prioritization is least-first, i.e. larger -- values are nicer. -- -- A Queue is not associated with any working thread, therefore, -- it is the client's responsibility to make sure that every pushed task -- is also pulled, or the Queue will stall. There are several ways -- to accomplish this: -- -- data (Ord a) => Queue a data TaskHandle a data QueueOrder FIFO :: QueueOrder FILO :: QueueOrder -- | Configuration options for a Queue. A Queue blocks on a -- number of predicates when dispatching a job. Generally, -- fair_queue_configuration should work well for long-running -- batch jobs and fast_queue_configuration should work for rapid -- paced jobs. -- -- -- -- If a task is blocked for any reason, the task is skipped and the next -- task attempted, in priority order. data (Ord a) => QueueConfigurationRecord a QueueConfigurationRecord :: STM () -> (a -> STM ()) -> (a -> a -> Bool) -> Int -> !QueueOrder -> QueueConfigurationRecord a -- | A predicate that must hold before any task may be pulled from a -- Queue. queue_predicate :: QueueConfigurationRecord a -> STM () -- | A predicate that must hold before any priority level may be pulled -- from a Queue. priority_indexed_predicate :: QueueConfigurationRecord a -> (a -> STM ()) -- | Constrains the greatest allowed difference between the priority of the -- top-of-queue task and the priority of a task to be pulled. allowed_priority_inversion :: QueueConfigurationRecord a -> a -> a -> Bool -- | The greatest allowed difference between the ideal prioritized -- FILO/FIFO ordering of tasks and the actual ordering of tasks. Setting -- this too high can introduce a lot of overhead in the presence of a lot -- of short-running tasks. Setting this to zero turns off the predicate -- failover feature, i.e. only the top of queue task will ever be pulled. allowed_ordering_inversion :: QueueConfigurationRecord a -> Int -- | Should the Queue run in FILO or FIFO order. Ordering takes -- place after prioritization, and won't have much effect if priorities -- are very fine-grained. queue_order :: QueueConfigurationRecord a -> !QueueOrder -- | A queue tuned for high throughput and fairness when processing -- moderate to long running tasks. fair_queue_configuration :: (Ord a) => QueueConfigurationRecord a -- | A queue tuned for high responsiveness and low priority inversion, but -- may have poorer long-term throughput and potential to starve some -- tasks compared to fair_queue_configuration. fast_queue_configuration :: (Ord a) => QueueConfigurationRecord a -- | Create a new Queue. newQueue :: (Ord a) => QueueConfigurationRecord a -> IO (Queue a) taskPriority :: TaskHandle a -> a taskQueue :: TaskHandle a -> Queue a pendingTasks :: (Ord a) => Queue a -> STM [TaskHandle a] -- | True iff this task is poised at the top of it's Queue. isTopOfQueue :: TaskHandle a -> STM Bool hasCompleted :: TaskHandle a -> STM Bool -- | Put a task with it's priority value onto this queue. Returns a handle -- to the task. putTask :: (Ord a) => Queue a -> a -> STM () -> STM (TaskHandle a) -- | Pull and commit a task from this Queue. pullTask :: (Ord a) => Queue a -> STM (TaskHandle a) -- | Pull this task from the top of a Queue, if it is already there. -- If this task is top-of-queue, but it's predicates fail, then -- pullFromTop may instead pull a lower-priority -- TaskHandle. pullFromTop :: (Ord a) => TaskHandle a -> STM (TaskHandle a) -- | Don't return until the given TaskHandles have been pulled from -- their associated Queues. This doesn't guarantee that the -- TaskHandle will ever be pulled, even when the TaskHandle -- and Queue are both viable. You must concurrently arrange for -- every other TaskHandle associated with the same Queue to -- be pulled, or the Queue will stall. pullSpecificTasks -- can handle lists TaskHandles that are distributed among several -- Queues, as well as a TaskHandles that have already -- completed or complete concurrently from another thread. pullSpecificTasks :: (Ord a) => [TaskHandle a] -> IO () -- | "Fire and forget" some tasks on a separate thread. dispatchTasks :: (Ord a) => [(Queue a, a, STM ())] -> IO [TaskHandle a] -- | Process a Queue until it is empty. flushQueue :: (Ord a) => Queue a -> IO () -- | The number of tasks pending on this Queue. load :: (Ord a) => Queue a -> STM Int instance (Ord a) => Ord (TaskHandle a) instance (Ord a, Eq a) => Eq (TaskHandle a) instance (Ord a) => Ord (Queue a) instance (Ord a) => Eq (Queue a) module Control.Concurrent.Priority.Room -- | A resource pool, parameterized against arbitrary user data. data Room u -- | Create a new Room with some arbitrary user data. newRoom :: u -> IO (Room u) -- | Get all ThreadIds that are currently claimimg this Room. inUse :: Room u -> STM (Set ThreadId) -- | A Claim, or attempt to acquire or release a Room. data Claim u -- | Get the Room target of a Claim. claimedRoom :: Claim u -> Room u -- | Get the thread attempting a Claim. claimedThread :: Claim u -> ThreadId -- | Get the user data associated with a Room. userData :: Room u -> u class RoomGroup m roomsOf :: (RoomGroup m) => m -> [Room (UserData m)] class RoomConstraint u approveConstraint :: (RoomConstraint u) => Claim a -> u -> STM () -- | Rules for calling claim_. The two major contexts are -- DefaultRoomContext, which uses RoomConstraints to -- determine which Rooms are available, and -- UnconstrainedRoomContext, which does not place any constraints -- on any Room. class BaseRoomContext c where { type family BaseRoomContextData c :: *; } approveClaimsEntering :: (BaseRoomContext c) => c -> [Claim (UserData c)] -> STM (BaseRoomContextData c) approveClaimsExiting :: (BaseRoomContext c) => c -> [Claim (UserData c)] -> STM (BaseRoomContextData c) waitingAction :: (BaseRoomContext c) => c -> (BaseRoomContextData c) -> STM () -- | An indirect reference to a BaseRoomContext. class RoomContext c where { type family Base c :: *; } baseContext :: (RoomContext c) => c -> Base c -- | A maximum limit on the number of threads allowed to claim a room. newtype MaxThreads MaxThreads :: Int -> MaxThreads data ClaimMode Acquire :: ClaimMode Release :: ClaimMode -- | Require that all RoomConstraints be satisfied when acquiring a -- Room. This is the default. data DefaultRoomContext u Default :: DefaultRoomContext u -- | Don't check any RoomConstraints when acquiring a Room. data UnconstrainedRoomContext u Unconstrained :: UnconstrainedRoomContext u -- | Temporarily Acquire, and then release, or Release, and -- then acquire, some Rooms for the duration of a critical -- section. A simple example where a room might be used to prevent -- interleaving of stdout: -- --
--   room <- newRoom (MaxThreads 1)
--   forkIO $ claim Acquire room $ putStrLn "Hello World!"
--   forkIO $ claim Acquire room $ putStrLn "Foo!  Bar!"
--   
claim :: (RoomGroup c, RoomContext c, BaseRoomContext (Base c), (UserData c) ~ (UserData (Base c))) => ClaimMode -> c -> IO a -> IO a -- | approve some claims according to their constraints. approveClaims :: (RoomConstraint u) => [Claim u] -> STM () instance (Base m ~ DefaultRoomContext (UserData m), BaseRoomContext c) => RoomContext (c, m) instance (RoomConstraint u) => RoomContext [Room u] instance (RoomConstraint u) => RoomContext (Room u) instance (Base m ~ DefaultRoomContext (UserData m), BaseRoomContext c) => BaseRoomContext (c, m) instance BaseRoomContext (UnconstrainedRoomContext u) instance (RoomConstraint u) => BaseRoomContext (DefaultRoomContext u) instance (UserData c ~ UserData m, RoomGroup c, RoomGroup m) => RoomGroup (c, m) instance RoomGroup (UnconstrainedRoomContext u) instance RoomGroup (DefaultRoomContext u) instance RoomGroup [Room u] instance RoomGroup (Room u) module Control.Concurrent.Priority.Schedule -- | Schedule a task to run from a prioritized Queue. -- -- Tasks that do not actually make claims against any of the -- Schedules internal Rooms will skip scheduling and the -- Rooms will be claimed immediately using -- DefaultRoomContext. This is usually what you want, in -- particular in the case where no rooms are actually being claimed, e.g. -- reentrant scheduling. -- -- In other words: -- -- Always wrong: -- --
--   (Schedule q 2 Default,[room1,room2])
--   
-- -- Right: -- --
--   Schedule q 2 (Default,[room1,room2])
--   
-- -- Alternately, if you only want to schedule access to room1, -- you can place room1 internally and room2 externally. -- Schedule will be smart about when to schedule and when not to -- schedule: -- --
--   (Schedule q 2 (Default,room1), room2)
--   
-- -- The Default applies internally and externally to the -- Schedule. In the following example, Unconstrained -- applies to both room1 and room2: -- --
--   (Schedule q 2 (Unconstrained,room1), room2)
--   
data Schedule p c Schedule :: (Queue p) -> p -> c -> Schedule p c instance (BaseRoomContext (Schedule p c)) => RoomContext (Schedule p c) instance (BaseRoomContextData c ~ (), Ord p, RoomGroup c, BaseRoomContext c) => BaseRoomContext (Schedule p c) instance (RoomGroup c) => RoomGroup (Schedule p c) -- | A prioritized TaskPool. This consists of a Queue, which -- prioritizes tasks, and a Room which restricts the number of -- tasks that may execute at one time. module Control.Concurrent.Priority.TaskPool data TaskPool p u -- | A RoomContext for a task pool. schedule :: TaskPool p u -> p -> (Schedule p (DefaultRoomContext (TaskPoolConstraint u), Room (TaskPoolConstraint u))) -- | Create a new TaskPool. TaskPools begin stopped, use -- startQueue to start. -- -- -- -- Consider using simpleTaskPool if you have no special needs. newTaskPool :: (Ord p) => QueueConfigurationRecord p -> Int -> u -> IO (TaskPool p u) -- | Just create a new TaskPool. The task pool is constrained by the -- number of capabilities indicated by numCapabilities. simpleTaskPool :: (Ord p) => IO (TaskPool p ()) poolRoom :: TaskPool p u -> Room (TaskPoolConstraint u) poolQueue :: TaskPool p u -> Queue p startQueue :: TaskPool p u -> IO () stopQueue :: TaskPool p u -> IO () -- | The number of threads participating in this ThreadPool. activity :: (Ord p) => TaskPool p u -> STM Int instance RoomContext (TaskPool () u) instance RoomGroup (TaskPool p u)