async 2.2.2 → 2.2.3
raw patch · 3 files changed
+160/−78 lines, 3 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- Control/Concurrent/Async.hs +152/−74
- async.cabal +4/−4
- changelog.md +4/−0
Control/Concurrent/Async.hs view
@@ -24,65 +24,123 @@ -- "Control.Concurrent". The main additional functionality it -- provides is the ability to wait for the return value of a thread, -- but the interface also provides some additional safety and--- robustness over using threads and @MVar@ directly.+-- robustness over using 'forkIO' threads and @MVar@ directly. --+-- == High-level API+--+-- @async@'s high-level API spawns /lexically scoped/ threads,+-- ensuring the following key poperties that make it safer to use+-- than using plain 'forkIO':+--+-- 1. No exception is swallowed (waiting for results propagates exceptions).+-- 2. No thread is leaked (left running unintentionally).+--+-- (This is done using the 'Control.Exception.bracket' pattern to work in presence+-- of synchornous and asynchronous exceptions.)+--+-- __Most practical/production code should only use the high-level API__.+-- -- The basic type is @'Async' a@, which represents an asynchronous -- @IO@ action that will return a value of type @a@, or die with an--- exception. An @Async@ corresponds to a thread, and its 'ThreadId'--- can be obtained with 'asyncThreadId', although that should rarely--- be necessary.+-- exception. An 'Async' is a wrapper around a low-level 'forkIO' thread. --+-- The fundamental function to spawn threads with the high-level API is+-- 'withAsync'.+-- -- For example, to fetch two web pages at the same time, we could do -- this (assuming a suitable @getURL@ function): ----- > do a1 <- async (getURL url1)--- > a2 <- async (getURL url2)--- > page1 <- wait a1--- > page2 <- wait a2--- > ...+-- > withAsync (getURL url1) $ \a1 -> do+-- > withAsync (getURL url2) $ \a2 -> do+-- > page1 <- wait a1+-- > page2 <- wait a2+-- > ... ----- where 'async' starts the operation in a separate thread, and--- 'wait' waits for and returns the result. If the operation--- throws an exception, then that exception is re-thrown by--- 'wait'. This is one of the ways in which this library--- provides some additional safety: it is harder to accidentally--- forget about exceptions thrown in child threads.+-- where 'withAsync' starts the operation in a separate thread, and+-- 'wait' waits for and returns the result. ----- A slight improvement over the previous example is this:+-- * If the operation throws an exception, then that exception is re-thrown+-- by 'wait'. This ensures property (1): No exception is swallowed.+-- * If an exception bubbles up through a 'withAsync', then the 'Async'+-- it spawned is 'cancel'ed. This ensures property (2): No thread is leaked. ----- > withAsync (getURL url1) $ \a1 -> do--- > withAsync (getURL url2) $ \a2 -> do--- > page1 <- wait a1--- > page2 <- wait a2--- > ...+-- Often we do not care to work manually with 'Async' handles like+-- @a1@ and @a2@. Instead, we want to express high-level objectives like+-- performing two or more tasks concurrently, and waiting for one or all+-- of them to finish. --+-- For example, the pattern of performing two IO actions concurrently and+-- waiting for both their results is packaged up in a combinator 'concurrently',+-- so we can further shorten the above example to:+--+-- > (page1, page2) <- concurrently (getURL url1) (getURL url2)+-- > ...+--+-- The section __/High-level utilities/__ covers the most+-- common high-level objectives, including:+--+-- * Waiting for 2 results ('concurrently').+-- * Waiting for many results ('mapConcurrently' / 'forConcurrently').+-- * Waiting for the first of 2 results ('race').+-- * Waiting for arbitrary nestings of "all of /N/" and "the first of /N/"+-- results with the 'Concurrently' newtype and its 'Applicative' and+-- 'Alternative' instances.+--+-- Click here to scroll to that section:+-- "Control.Concurrent.Async#high-level-utilities".+--+-- == Low-level API+--+-- Some use cases require parallelism that is not lexically scoped.+--+-- For those, the low-level function 'async' can be used as a direct+-- equivalent of 'forkIO':+--+-- > -- Do NOT use this code in production, it has a flaw (explained below).+-- > do+-- > a1 <- async (getURL url1)+-- > a2 <- async (getURL url2)+-- > page1 <- wait a1+-- > page2 <- wait a2+-- > ...+--+-- In contrast to 'withAsync', this code has a problem.+--+-- It still fulfills property (1) in that an exception arising from+-- @getUrl@ will be re-thrown by 'wait', but it does not fulfill+-- property (2).+-- Consider the case when the first 'wait' throws an exception; then the+-- second 'wait' will not happen, and the second 'async' may be left+-- running in the background, possibly indefinitely.+-- -- 'withAsync' is like 'async', except that the 'Async' is -- automatically killed (using 'uninterruptibleCancel') if the--- enclosing IO operation returns before it has completed. Consider--- the case when the first 'wait' throws an exception; then the second--- 'Async' will be automatically killed rather than being left to run--- in the background, possibly indefinitely. This is the second way--- that the library provides additional safety: using 'withAsync'--- means we can avoid accidentally leaving threads running.+-- enclosing IO operation returns before it has completed. -- Furthermore, 'withAsync' allows a tree of threads to be built, such -- that children are automatically killed if their parents die for any -- reason. ----- The pattern of performing two IO actions concurrently and waiting--- for their results is packaged up in a combinator 'concurrently', so--- we can further shorten the above example to:+-- If you need to use the low-level API, ensure that you gurantee+-- property (2) by other means, such as 'link'ing asyncs that need+-- to die together, and protecting against asynchronous exceptions+-- using 'Control.Exception.bracket', 'Control.Exception.mask',+-- or other functions from "Control.Exception". ----- > (page1, page2) <- concurrently (getURL url1) (getURL url2)--- > ...+-- == Miscellaneous -- -- The 'Functor' instance can be used to change the result of an -- 'Async'. For example: ----- > ghci> a <- async (return 3)--- > ghci> wait a--- > 3--- > ghci> wait (fmap (+1) a)+-- > ghci> withAsync (return 3) (\a -> wait (fmap (+1) a)) -- > 4+--+-- === Resource exhaustion+--+-- As with all concurrent programming, keep in mind that while+-- Haskell's cooperative ("green") multithreading carries low overhead,+-- spawning too many of them at the same time may lead to resource exhaustion+-- (of memory, file descriptors, or other limited resources), given that the+-- actions running in the threads consume these resources. ----------------------------------------------------------------------------- @@ -90,9 +148,9 @@ -- * Asynchronous actions Async,- -- ** Spawning- async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask, + -- * High-level API+ -- ** Spawning with automatic 'cancel'ation withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask, withAsyncOnWithUnmask,@@ -101,33 +159,40 @@ wait, poll, waitCatch, asyncThreadId, cancel, uninterruptibleCancel, cancelWith, AsyncCancelled(..), - -- ** STM operations+ -- ** #high-level-utilities# High-level utilities+ race, race_,+ concurrently, concurrently_,+ mapConcurrently, forConcurrently,+ mapConcurrently_, forConcurrently_,+ replicateConcurrently, replicateConcurrently_,+ Concurrently(..),+ compareAsyncs,++ -- ** Specialised operations++ -- *** STM operations waitSTM, pollSTM, waitCatchSTM, - -- ** Waiting for multiple 'Async's+ -- *** Waiting for multiple 'Async's waitAny, waitAnyCatch, waitAnyCancel, waitAnyCatchCancel, waitEither, waitEitherCatch, waitEitherCancel, waitEitherCatchCancel, waitEither_, waitBoth, - -- ** Waiting for multiple 'Async's in STM+ -- *** Waiting for multiple 'Async's in STM waitAnySTM, waitAnyCatchSTM, waitEitherSTM, waitEitherCatchSTM, waitEitherSTM_, waitBothSTM, + -- * Low-level API++ -- ** Spawning (low-level API)+ async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask,+ -- ** Linking link, linkOnly, link2, link2Only, ExceptionInLinkedThread(..), - -- * Convenient utilities- race, race_,- concurrently, concurrently_,- mapConcurrently, forConcurrently,- mapConcurrently_, forConcurrently_,- replicateConcurrently, replicateConcurrently_,- Concurrently(..),- compareAsyncs,- ) where import Control.Concurrent.STM@@ -185,11 +250,16 @@ instance Functor Async where fmap f (Async a w) = Async a (fmap (fmap f) w) --- | Compare two Asyncs that may have different types by their ThreadId.+-- | Compare two Asyncs that may have different types by their 'ThreadId'. compareAsyncs :: Async a -> Async b -> Ordering compareAsyncs (Async t1 _) (Async t2 _) = compare t1 t2 -- | Spawn an asynchronous action in a separate thread.+--+-- Like for 'forkIO', the action may be left running unintentinally+-- (see module-level documentation for details).+--+-- __Use 'withAsync' style functions wherever you can instead!__ async :: IO a -> IO (Async a) async = inline asyncUsing rawForkIO @@ -516,7 +586,10 @@ -- {-# INLINE waitBoth #-} waitBoth :: Async a -> Async b -> IO (a,b)-waitBoth left right = atomically (waitBothSTM left right)+waitBoth left right = tryAgain $ atomically (waitBothSTM left right)+ where+ -- See: https://github.com/simonmar/async/issues/14+ tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f -- | A version of 'waitBoth' that can be used inside an STM transaction. --@@ -647,6 +720,11 @@ -- > waitBoth a b concurrently :: IO a -> IO b -> IO (a,b) +-- | 'concurrently', but ignore the result values+--+-- @since 2.1.1+concurrently_ :: IO a -> IO b -> IO ()+ #define USE_ASYNC_VERSIONS 0 #if USE_ASYNC_VERSIONS@@ -656,16 +734,15 @@ withAsync right $ \b -> waitEither a b -race_ left right =- withAsync left $ \a ->- withAsync right $ \b ->- waitEither_ a b+race_ left right = void $ race left right concurrently left right = withAsync left $ \a -> withAsync right $ \b -> waitBoth a b +concurrently_ left right = void $ concurrently left right+ #else -- MVar versions of race/concurrently@@ -744,9 +821,19 @@ stop return r +concurrently_ left right = concurrently' left right (collect 0)+ where+ collect 2 _ = return ()+ collect i m = do+ e <- m+ case e of+ Left ex -> throwIO ex+ Right _ -> collect (i + 1 :: Int) m++ #endif --- | maps an @IO@-performing function over any @Traversable@ data+-- | Maps an 'IO'-performing function over any 'Traversable' data -- type, performing all the @IO@ actions concurrently, and returning -- the original data structure with the arguments replaced by the -- results.@@ -758,6 +845,10 @@ -- -- > pages <- mapConcurrently getURL ["url1", "url2", "url3"] --+-- Take into account that @async@ will try to immediately spawn a thread+-- for each element of the @Traversable@, so running this on large+-- inputs without care may lead to resource exhaustion (of memory,+-- file descriptors, or other limited resources). mapConcurrently :: Traversable t => (a -> IO b) -> t a -> IO (t b) mapConcurrently f = runConcurrently . traverse (Concurrently . f) @@ -769,28 +860,15 @@ forConcurrently :: Traversable t => t a -> (a -> IO b) -> IO (t b) forConcurrently = flip mapConcurrently --- | `mapConcurrently_` is `mapConcurrently` with the return value discarded,--- just like @mapM_@.+-- | `mapConcurrently_` is `mapConcurrently` with the return value discarded;+-- a concurrent equivalent of 'mapM_'. mapConcurrently_ :: F.Foldable f => (a -> IO b) -> f a -> IO () mapConcurrently_ f = runConcurrently . F.foldMap (Concurrently . void . f) --- | `forConcurrently_` is `forConcurrently` with the return value discarded,--- just like @forM_@.+-- | `forConcurrently_` is `forConcurrently` with the return value discarded;+-- a concurrent equivalent of 'forM_'. forConcurrently_ :: F.Foldable f => f a -> (a -> IO b) -> IO () forConcurrently_ = flip mapConcurrently_---- | 'concurrently', but ignore the result values------ @since 2.1.1-concurrently_ :: IO a -> IO b -> IO ()-concurrently_ left right = concurrently' left right (collect 0)- where- collect 2 _ = return ()- collect i m = do- e <- m- case e of- Left ex -> throwIO ex- Right _ -> collect (i + 1 :: Int) m -- | Perform the action in the given number of threads. --
async.cabal view
@@ -1,5 +1,5 @@ name: async-version: 2.2.2+version: 2.2.3 -- don't forget to update ./changelog.md! synopsis: Run IO operations asynchronously and wait for their results @@ -34,7 +34,7 @@ cabal-version: >=1.10 homepage: https://github.com/simonmar/async bug-reports: https://github.com/simonmar/async/issues-tested-with: GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2, GHC==7.2.2, GHC==7.0.4+tested-with: GHC==8.10.1, GHC==8.8.3, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2, GHC==7.2.2, GHC==7.0.4 extra-source-files: changelog.md@@ -50,14 +50,14 @@ if impl(ghc>=7.1) other-extensions: Trustworthy exposed-modules: Control.Concurrent.Async- build-depends: base >= 4.3 && < 4.14, hashable >= 1.1.2.0 && < 1.4, stm >= 2.2 && < 2.6+ build-depends: base >= 4.3 && < 4.16, hashable >= 1.1.2.0 && < 1.4, stm >= 2.2 && < 2.6 test-suite test-async default-language: Haskell2010 type: exitcode-stdio-1.0 hs-source-dirs: test main-is: test-async.hs- build-depends: base >= 4.3 && < 4.14,+ build-depends: base >= 4.3 && < 4.15, async, stm, test-framework,
changelog.md view
@@ -1,3 +1,7 @@+## Changes in 2.2.3:++ - Documentation fixes+ ## Changes in 2.2.2: - Builds with GHC 8.6.x