packages feed

core-program 0.6.8.0 → 0.6.9.2

raw patch · 5 files changed

+333/−2 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Core.Program.Execute: modifyApplicationState :: (τ -> Program τ τ) -> Program τ ()
+ Core.Program.Threads: Timeout :: Timeout
+ Core.Program.Threads: data Timeout
+ Core.Program.Threads: instance GHC.Exception.Type.Exception Core.Program.Threads.Timeout
+ Core.Program.Threads: instance GHC.Show.Show Core.Program.Threads.Timeout
+ Core.Program.Threads: timeoutThread :: Rational -> Program τ α -> Program τ α
+ Core.Program.Workers: data Queue α
+ Core.Program.Workers: finishQueue :: Queue α -> Program τ ()
+ Core.Program.Workers: getMachineSize :: Program τ Int
+ Core.Program.Workers: mapWorkers :: Int -> (α -> Program τ β) -> [α] -> Program τ [β]
+ Core.Program.Workers: newQueue :: Program τ (Queue α)
+ Core.Program.Workers: runWorkers_ :: Int -> (α -> Program τ ()) -> Queue α -> Program τ ()
+ Core.Program.Workers: unQueue :: Queue α -> TQueue (Maybe α)
+ Core.Program.Workers: writeQueue :: Queue α -> α -> Program τ ()
+ Core.Program.Workers: writeQueue' :: Foldable ω => Queue α -> ω α -> Program τ ()

Files

core-program.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.1.+-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack  name:           core-program-version:        0.6.8.0+version:        0.6.9.2 synopsis:       Opinionated Haskell Interoperability description:    A library to help build command-line programs, both tools and                 longer-running daemons.@@ -45,6 +45,7 @@       Core.Program.Notify       Core.Program.Threads       Core.Program.Unlift+      Core.Program.Workers       Core.System       Core.System.Base       Core.System.External
lib/Core/Program.hs view
@@ -22,6 +22,7 @@       -- and more.       module Core.Program.Execute     , module Core.Program.Threads+    , module Core.Program.Workers     , module Core.Program.Unlift     , module Core.Program.Metadata     , module Core.Program.Exceptions@@ -55,3 +56,4 @@ import Core.Program.Notify import Core.Program.Threads import Core.Program.Unlift+import Core.Program.Workers
lib/Core/Program/Execute.hs view
@@ -77,6 +77,7 @@     , getConsoleWidth     , getApplicationState     , setApplicationState+    , modifyApplicationState     , changeProgram        -- * Useful actions@@ -602,6 +603,44 @@     liftIO $ do         let v = applicationDataFrom context         modifyMVar_ v (\_ -> pure user)++{- |+Modify the user supplied top-level application state in a single atomic action+combining getting the value and replacing it. Following the pattern of other+@modify@ functions in the Haskell ecosystem, this takes a function which+allows you to take limited actions with the existing value, returning the new+value that should be stored.++@++    'modifyApplicationState'+        ( \settings{answer = a} ->+            'pure'+                (settings+                    { answer = a + 1+                    }+                )+        )+@++While the function you need to supply is in 'Program' @τ@ and so able to do+general work if necessary, some care should be taken to return from the action+as quickly as possible; this call will be blocking other consumers of the+top-level application state until it returns.++@since 0.6.9+-}+modifyApplicationState :: (τ -> Program τ τ) -> Program τ ()+modifyApplicationState program = do+    context <- ask+    liftIO $ do+        let v = applicationDataFrom context+        modifyMVar_+            v+            ( \user -> do+                user' <- subProgram context (program user)+                pure user'+            )  {- | Sometimes you need to change the type of the application state from what is
lib/Core/Program/Threads.hs view
@@ -40,11 +40,13 @@     , concurrentThreads_     , raceThreads     , raceThreads_+    , timeoutThread        -- * Internals     , Thread     , unThread     , Terminator (..)+    , Timeout (..)     ) where  import Control.Concurrent (ThreadId, forkIO, killThread)@@ -60,6 +62,7 @@ import Control.Monad.Reader.Class (MonadReader (ask)) import Core.Data.Structures import Core.Program.Context+import Core.Program.Execute import Core.Program.Logging import Core.System.Base import Core.Text.Rope@@ -511,3 +514,34 @@ linkThread :: Thread α -> Program τ () linkThread _ = pure () {-# DEPRECATED linkThread "Exceptions are bidirectional so linkThread no longer needed" #-}++{-|+If a timeout is exceeded this exception will be thrown by 'timeoutThread'.++@since 0.6.9+-}+data Timeout = Timeout deriving (Show)++instance Exception Timeout++{- |+Run a program that needs to complete before the given number of seconds have+elapsed. This will return the result of the sub program or throw the 'Timeout'+exception if the limit is exceeded.++@since 0.6.9+-}+timeoutThread :: Rational -> Program τ α -> Program τ α+timeoutThread seconds program = do+    result <-+        raceThreads+            ( do+                sleepThread seconds+                pure Timeout+            )+            ( do+                program+            )+    case result of+        Left e -> throw e+        Right a -> pure a
+ lib/Core/Program/Workers.hs view
@@ -0,0 +1,255 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StrictData #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_HADDOCK prune #-}++{- |+Utility functions for building programs which consume work off of a queue.++Frequently you need to receive items from an external system and perform work+on them. One way to structure such a program is to feed the items into a queue+and then consume those items one at a time. That, of course, is+slow—especially when then worker has to itself carry out computationally+intensive tasks or interact itself with external systems. So we want to have+multiple workers running, but only to an extent limited by the number of cores+available, the number of external connections allowed, or some other+constraint.++This library allows you to add items to a queue, then launch worker threads to+consume those items at up to a specified maximum amount of concurrency.+-}+module Core.Program.Workers+    ( -- * Work Queue+      newQueue+    , writeQueue+    , writeQueue'+    , finishQueue++      -- * Worker Threads+    , runWorkers_+    , mapWorkers++      -- * Internals+    , Queue+    , unQueue+    , getMachineSize+    ) where++import Control.Concurrent.STM (atomically)+import Control.Concurrent.STM.TQueue (TQueue, flushTQueue, newTQueueIO, readTQueue, unGetTQueue, writeTQueue)+import Control.Monad+    ( forM+    )+import Core.Program.Context+import Core.Program.Threads+import Core.System.Base+import GHC.Conc (getNumCapabilities)++{- |+Report back the number of processor cores that are available as Haskell+"capabilities" (this was set when you launched the program with+'Core.Program.Execute.execute'). This can best be used to set the number of+concurrent worker threads when running 'runWorkers_' or 'mapWorkers'.++@since 0.6.9+-}+getMachineSize :: Program τ Int+getMachineSize = liftIO $ do+    getNumCapabilities++{- |+A queue which has an end, someday.++(this is a thin wrapper over the __stm__ 'TQueue' type)++@since 0.6.9+-}+newtype Queue α = Queue (TQueue (Maybe α))++{- |+Initialize a new queue.++@since 0.6.9+-}+newQueue :: Program τ (Queue α)+newQueue = do+    queue <- liftIO $ do+        newTQueueIO+    pure (Queue queue)++{- |+Add an item to the queue.++@since 0.6.9+-}+writeQueue :: Queue α -> α -> Program τ ()+writeQueue (Queue queue) item = do+    liftIO $ do+        atomically $ do+            writeTQueue queue (Just item)++{- |+Add a list of items to the queue.++@since 0.6.9+-}+writeQueue' :: Foldable ω => Queue α -> ω α -> Program τ ()+writeQueue' (Queue queue) items = do+    liftIO $ do+        atomically $ do+            mapM_+                ( \item ->+                    writeTQueue queue (Just item)+                )+                items++{- |+Indicate that you are finished adding queue, thereby allowing the worker+threads consuming from the queue to complete and return.++Remember that you can call at any time, even before you have launched the+worker threads with 'runWorkers_'.++@since 0.6.9+-}+finishQueue :: Queue α -> Program τ ()+finishQueue (Queue queue) = do+    liftIO $ do+        atomically $ do+            writeTQueue queue Nothing++{- |+Access the underlying queue. We make use of the STM 'TQueue' type, so you'll+want the following imports:++@+import "Control.Concurrent.STM" ('atomically')+import "Control.Concurrent.STM.TQueue" ('TQueue', 'writeTQueue')+@++Having accessed the underlying queue you can write items, wrapped in 'Just', to+it directly:++@+    'liftIO' $ do+        'atomically' $ do+            'writeTQueue' queue ('Just' item)+@++A 'Nothing' written to the underlying queue will signal the worker threads+that the end of input has been reached and they can safely return.++@since 0.6.9+-}+unQueue :: Queue α -> TQueue (Maybe α)+unQueue (Queue queue) = queue++{- |+Run a pool of worker threads which consume items off the work queue.++Once you have an action that enqueues items with 'writeQueue' you can then+launch the worker threads:++@+    'runWorkers_' 16 worker queue+@++consuming 16 items at a time concurrently in this example.++It is assumed that the workers have a way of communicating their results+onwards, either because they are side-effecting in the real world themselves,+or because you have passed in some  'Control.Concurrent.MVar' or 'TQueue' to+collect the results.++@since 0.6.9+-}+runWorkers_ :: Int -> (α -> Program τ ()) -> Queue α -> Program τ ()+runWorkers_ n action (Queue queue) = do+    createScope $ do+        ts <- forM [1 .. n] $ \_ -> do+            forkThread $ do+                loop+        _ <- waitThreads' ts+        pure ()+  where+    loop = do+        possibleItem <- liftIO $ do+            atomically $ do+                readTQueue queue -- blocks+        case possibleItem of+            Nothing -> do+                --+                -- We put the Nothing back so that other workers can also shutdown.+                --+                liftIO $ do+                    atomically $ do+                        unGetTQueue queue Nothing+            Just item -> do+                --+                -- Do the work+                --+                action item+                loop++{- |+Map a pool of workers over a list concurrently.++Simply forking one Haskell thread for every item in a list is a suprisingly+reasonable choice in many circumstances given how good Haskell's concurrency+machinery is, and in this library can be achieved by 'Control.Monad.forM'ing+'forkThread' over a list of items. But if you need tighter control over the+amount of concurrency—as is often the case when doing something+computationally heavy or making requests of an external service with known+limitations—then you are better off using this convenience function.++(this was originally modelled on __async__\'s+'Control.Concurrent.Async.mapConcurrently'. That implementation has the+drawback that the number of threads created is set by the size of the+structure being traversed. Here we set the amount of concurrency explicitly.)++Be aware that the order of items in the output list is non-deterministic and+will depend on the order that the action function completes, not the order of+items in the input.++@since 0.6.9+-}+mapWorkers :: Int -> (α -> Program τ β) -> [α] -> Program τ [β]+mapWorkers n action items = do+    inputs <- newQueue++    outputs <- liftIO $ do+        newTQueueIO :: IO (TQueue β)++    --+    -- Load the input list into a queue followed by a terminator.+    --++    writeQueue' inputs items+    finishQueue inputs++    --+    -- Invoke the general concurrent workers tool above to process the queue.+    --++    runWorkers_+        n+        ( \item -> do+            result <- action item+            liftIO $ do+                atomically $ do+                    writeTQueue outputs result+        )+        inputs++    --+    -- Convert the results back to a list.+    --++    liftIO $ do+        atomically $ do+            flushTQueue outputs