packages feed

core-program 0.4.4.0 → 0.4.5.0

raw patch · 3 files changed

+140/−14 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Core.Program.Execute: catch :: (MonadCatch m, Exception e) => m a -> (e -> m a) -> m a
+ Core.Program.Execute: catchesAsync :: (MonadCatch m, MonadThrow m) => m a -> [Handler m a] -> m a
+ Core.Program.Execute: throw :: (MonadThrow m, Exception e) => e -> m a
+ Core.Program.Execute: try :: (MonadCatch m, Exception e) => m a -> m (Either e a)
+ Core.Program.Execute: tryAsync :: (MonadCatch m, Exception e) => m a -> m (Either e a)
+ Core.Program.Threads: cancelThread :: Thread α -> Program τ ()
+ Core.Program.Threads: waitThread' :: Thread α -> Program τ (Either SomeException α)
+ Core.Program.Threads: waitThreads' :: [Thread α] -> Program τ [Either SomeException α]

Files

core-program.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           core-program-version:        0.4.4.0+version:        0.4.5.0 synopsis:       Opinionated Haskell Interoperability description:    A library to help build command-line programs, both tools and                 longer-running daemons.
lib/Core/Program/Execute.hs view
@@ -83,6 +83,13 @@     resetTimer,     trap_, +    -- * Re-exports from safe-exports+    Safe.catch,+    Safe.catchesAsync,+    Safe.throw,+    Safe.try,+    Safe.tryAsync,+     -- * Internals     Context,     None (..),@@ -126,7 +133,7 @@     writeTQueue,  ) import qualified Control.Exception as Base (throwIO)-import qualified Control.Exception.Safe as Safe (catch, catchesAsync, throw)+import qualified Control.Exception.Safe as Safe (catch, catchesAsync, throw, try, tryAsync) import Control.Monad (     void,     when,
lib/Core/Program/Threads.hs view
@@ -27,7 +27,10 @@     forkThread,     waitThread,     waitThread_,+    waitThread',+    waitThreads',     linkThread,+    cancelThread,      -- * Helper functions     concurrentThreads,@@ -40,21 +43,23 @@     unThread, ) where -import Control.Concurrent.Async (Async)+import Control.Concurrent.Async (Async, AsyncCancelled, cancel) import qualified Control.Concurrent.Async as Async (     async,+    cancel,     concurrently,     concurrently_,     link,     race,     race_,     wait,+    waitCatch,  ) import Control.Concurrent.MVar (     newMVar,     readMVar,  )-import qualified Control.Exception.Safe as Safe (catch)+import qualified Control.Exception.Safe as Safe (catch, catchAsync) import Control.Monad (     void,  )@@ -78,14 +83,18 @@ Fork a thread. The child thread will run in the same @Context@ as the calling @Program@, including sharing the user-defined application state value. -If the code in the child thread throws an exception that is /not/ caught-within that thread, the exception will kill the thread. Threads dying without-telling anyone is a bit of an anti-pattern, so this library logs a-warning-level log message if this happens. If you additionally want the-exception to propegate back to the parent thread (say, for example, you want-your whole program to die if any of its worker threads fail), then call-'linkThread' after forking.+Threads that are launched off as children are on their own! If the code in the+child thread throws an exception that is /not/ caught within that thread, the+exception will kill the thread. Threads dying without telling anyone is a bit+of an anti-pattern, so this library logs a warning-level log message if this+happens. +If you additionally want the exception to propagate back to the parent thread+(say, for example, you want your whole program to die if any of its worker+threads fail), then call 'linkThread' after forking. If you want the other+direction, that is, if you want the forked thread to be cancelled when its+parent is cancelled, then you need to be waiting on it using 'waitThread'.+ (this wraps __async__\'s 'Control.Concurrent.Async.async' which in turn wraps __base__'s 'Control.Concurrent.forkIO') @@ -139,12 +148,28 @@ Wait for the completion of a thread, returning the result. This is a blocking operation. -(this wraps __async__\'s 'wait')+If the thread you are waiting on throws an exception it will be rethrown by+'waitThread'. +If the current thread making this call is cancelled (as a result of being on+the losing side of 'concurrentThreads' or 'raceThreads' for example, or due to+an explicit call to 'cancelThread'), then the thread you are waiting on will+be cancelled. This is necessary to ensure that child threads are not leaked if+you nest `forkThread`s.++(this wraps __async__\'s 'Control.Concurrent.Async.wait', taking care to+ensure the behaviour described above)+ @since 0.2.7 -} waitThread :: Thread α -> Program τ α-waitThread (Thread a) = liftIO $ Async.wait a+waitThread (Thread a) = liftIO $ do+    Safe.catchAsync+        (Async.wait a)+        ( \(e :: AsyncCancelled) -> do+            cancel a+            throw e+        )  {- | Wait for the completion of a thread, discarding its result. This is@@ -174,11 +199,90 @@ waitThread_ = void . waitThread  {- |+Wait for a thread to complete, returning the result if the computation was+successful or the exception if one was thrown by the child thread.++This basically is convenience for calling `waitThread` and putting `catch`+around it, but as with all the other @wait*@ functions this ensures that if+the thread waiting is cancelled the cancellation is propagated to the thread+being watched as well.++(this wraps __async__\'s 'Control.Concurrent.Async.waitCatch')++@since 0.4.5+-}+waitThread' :: Thread α -> Program τ (Either SomeException α)+waitThread' (Thread a) = liftIO $ do+    Safe.catchAsync+        ( do+            result <- Async.waitCatch a+            pure result+        )+        ( \(e :: AsyncCancelled) -> do+            cancel a+            throw e+        )++{- |+Wait for many threads to complete. This function is intended for the scenario+where you fire off a number of worker threads with `forkThread` but rather+than leaving them to run independantly, you need to wait for them all to+complete.++The results of the threads that complete successfully will be returned as+'Right' values. Should any of the threads being waited upon throw an+exception, those exceptions will be returned as 'Left' values.++If you don't need to analyse the failures individually, then you can just+collect the successes using "Data.Either"'s 'Data.Either.rights':++@+    responses <- 'waitThreads''++    'info' "Aggregating results..."+    combineResults ('Data.Either.rights' responses)+@++Likewise, if you /do/ want to do something with all the failures, you might+find 'Data.Either.lefts' useful:++@+    'mapM_' ('warn' . 'intoRope' . 'displayException') ('Data.Either.lefts' responses)+@++If the thread calling 'waitThreads'' is cancelled, then all the threads being+waited upon will also be cancelled. This often occurs within a timeout or+similar control measure implemented using 'raceThreads_'. Should the thread+that spawned all the workers and is waiting for their results be told to+cancel because it lost the "race", the child threads need to be told in turn+to cancel so as to avoid those threads being leaked and continuing to run as+zombies. This function takes care of that.++(this extends __async__\'s 'Control.Concurrent.Async.waitCatch' to work+across a list of Threads, taking care to ensure the cancellation behaviour+described throughout this module)++@since 0.4.5+-}+waitThreads' :: [Thread α] -> Program τ [Either SomeException α]+waitThreads' ts = liftIO $ do+    let as = fmap unThread ts+    Safe.catchAsync+        ( do+            results <- mapM Async.waitCatch as+            pure results+        )+        ( \(e :: AsyncCancelled) -> do+            mapM_ cancel as+            throw e+        )++{- | Ordinarily if an exception is thrown in a forked thread that exception is silently swollowed. If you instead need the exception to propegate back to the parent thread, you can \"link\" the two together using this function. -(this wraps __async__\'s 'link')+(this wraps __async__\'s 'Control.Concurrent.Async.link')  @since 0.4.2 -}@@ -186,6 +290,21 @@ linkThread (Thread a) = do     liftIO $ do         Async.link a++{- |+Cancel a thread.++(this wraps __async__\'s 'Control.Concurrent.Async.cancel'. The underlying+mechanism used is to throw the 'AsyncCancelled' to the other thread. That+exception is asynchronous, so will not be trapped by a 'catch' block and will+indeed cause the thread receiving the exception to come to an end)++@since 0.4.5+-}+cancelThread :: Thread α -> Program τ ()+cancelThread (Thread a) = do+    liftIO $ do+        Async.cancel a  {- | Fork two threads and wait for both to finish. The return value is the pair of