effectful-2.6.1.0: src/Effectful/Concurrent/Async.hs
-- | Lifted "Control.Concurrent.Async".
module Effectful.Concurrent.Async
( -- * Effect
Concurrent
-- ** Handlers
, runConcurrent
-- * Asynchronous actions
, Async
-- * High-level API
-- ** Spawning with automatic 'cancel'ation
, withAsync, withAsyncBound, withAsyncOn, withAsyncWithUnmask
, withAsyncOnWithUnmask
-- ** Querying 'Async's
, wait, poll, waitCatch, A.asyncThreadId
, cancel, cancelMany, uninterruptibleCancel, cancelWith
, A.AsyncCancelled(..)
, A.compareAsyncs
-- ** High-level utilities
, race, race_
, concurrently, concurrently_
, mapConcurrently, forConcurrently
, mapConcurrently_, forConcurrently_
, replicateConcurrently, replicateConcurrently_
-- *** Concurrently
, Concurrently(..)
-- *** Conc
, Conc, conc, runConc, U.ConcException(..)
-- ** Pooled concurrency
, pooledMapConcurrentlyN
, pooledMapConcurrently
, pooledMapConcurrentlyN_
, pooledMapConcurrently_
, pooledForConcurrentlyN
, pooledForConcurrently
, pooledForConcurrentlyN_
, pooledForConcurrently_
, pooledReplicateConcurrentlyN
, pooledReplicateConcurrently
, pooledReplicateConcurrentlyN_
, pooledReplicateConcurrently_
-- ** Specialised operations
-- *** STM operations
, A.waitSTM, A.pollSTM, A.waitCatchSTM
-- *** Waiting for multiple 'Async's
, waitAny, waitAnyCatch, waitAnyCancel, waitAnyCatchCancel
, waitEither, waitEitherCatch, waitEitherCancel, waitEitherCatchCancel
, waitEither_
, waitBoth
-- *** Waiting for multiple 'Async's in STM
, A.waitAnySTM, A.waitAnyCatchSTM
, A.waitEitherSTM, A.waitEitherCatchSTM
, A.waitEitherSTM_
, A.waitBothSTM
-- * Low-level API
-- ** Spawning (low-level API)
, async, asyncBound, asyncOn, asyncWithUnmask, asyncOnWithUnmask
-- ** Linking
, link, linkOnly, link2, link2Only, A.ExceptionInLinkedThread(..)
) where
import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (Async)
import Control.Exception (Exception, SomeException)
import Control.Monad (forever)
import Data.Kind (Type)
import Control.Concurrent.Async qualified as A
import UnliftIO.Async qualified as U
import UnliftIO.Internals.Async qualified as I
import Effectful
import Effectful.Concurrent.Effect
import Effectful.Dispatch.Static
import Effectful.Dispatch.Static.Primitive
import Effectful.Dispatch.Static.Unsafe
-- | Lifted 'A.async'.
async :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es (Async a)
async = liftAsync A.async
-- | Lifted 'A.asyncBound'.
asyncBound :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es (Async a)
asyncBound = liftAsync A.asyncBound
-- | Lifted 'A.asyncOn'.
asyncOn :: (HasCallStack, Concurrent :> es) => Int -> Eff es a -> Eff es (Async a)
asyncOn cpu = liftAsync (A.asyncOn cpu)
-- | Lifted 'A.asyncWithUnmask'.
asyncWithUnmask
:: (HasCallStack, Concurrent :> es)
=> ((forall b. Eff es b -> Eff es b) -> Eff es a)
-> Eff es (Async a)
asyncWithUnmask = liftAsyncWithUnmask A.asyncWithUnmask
-- | Lifted 'A.asyncOnWithUnmask'.
asyncOnWithUnmask
:: (HasCallStack, Concurrent :> es)
=> Int
-> ((forall b. Eff es b -> Eff es b) -> Eff es a)
-> Eff es (Async a)
asyncOnWithUnmask cpu = liftAsyncWithUnmask (A.asyncOnWithUnmask cpu)
-- | Lifted 'A.withAsync'.
withAsync
:: (HasCallStack, Concurrent :> es)
=> Eff es a
-> (Async a -> Eff es b)
-> Eff es b
withAsync = liftWithAsync A.withAsync
-- | Lifted 'A.withAsyncBound'.
withAsyncBound
:: (HasCallStack, Concurrent :> es)
=> Eff es a
-> (Async a -> Eff es b)
-> Eff es b
withAsyncBound = liftWithAsync A.withAsyncBound
-- | Lifted 'A.withAsyncOn'.
withAsyncOn
:: (HasCallStack, Concurrent :> es)
=> Int
-> Eff es a
-> (Async a -> Eff es b)
-> Eff es b
withAsyncOn cpu = liftWithAsync (A.withAsyncOn cpu)
-- | Lifted 'A.withAsyncWithUnmask'.
withAsyncWithUnmask
:: (HasCallStack, Concurrent :> es)
=> ((forall c. Eff es c -> Eff es c) -> Eff es a)
-> (Async a -> Eff es b)
-> Eff es b
withAsyncWithUnmask = liftWithAsyncWithUnmask A.withAsyncWithUnmask
-- | Lifted 'A.withAsyncOnWithUnmask'.
withAsyncOnWithUnmask
:: (HasCallStack, Concurrent :> es)
=> Int
-> ((forall c. Eff es c -> Eff es c) -> Eff es a)
-> (Async a -> Eff es b)
-> Eff es b
withAsyncOnWithUnmask cpu = liftWithAsyncWithUnmask (A.withAsyncOnWithUnmask cpu)
-- | Lifted 'A.wait'.
wait :: Concurrent :> es => Async a -> Eff es a
wait = unsafeEff_ . A.wait
-- | Lifted 'A.poll'.
poll
:: Concurrent :> es
=> Async a
-> Eff es (Maybe (Either SomeException a))
poll = unsafeEff_ . A.poll
-- | Lifted 'A.cancel'.
cancel :: Concurrent :> es => Async a -> Eff es ()
cancel = unsafeEff_ . A.cancel
-- | Lifted 'A.cancelMany'.
--
-- @since 2.6.1.0
cancelMany :: Concurrent :> es => [Async a] -> Eff es ()
cancelMany = unsafeEff_ . A.cancelMany
-- | Lifted 'A.cancelWith'.
cancelWith :: (Exception e, Concurrent :> es) => Async a -> e -> Eff es ()
cancelWith a = unsafeEff_ . A.cancelWith a
-- | Lifted 'A.uninterruptibleCancel'.
uninterruptibleCancel :: Concurrent :> es => Async a -> Eff es ()
uninterruptibleCancel = unsafeEff_ . A.uninterruptibleCancel
-- | Lifted 'A.waitCatch'.
waitCatch
:: Concurrent :> es
=> Async a
-> Eff es (Either SomeException a)
waitCatch = unsafeEff_ . A.waitCatch
-- | Lifted 'A.waitAny'.
waitAny :: Concurrent :> es => [Async a] -> Eff es (Async a, a)
waitAny = unsafeEff_ . A.waitAny
-- | Lifted 'A.waitAnyCatch'.
waitAnyCatch
:: Concurrent :> es
=> [Async a]
-> Eff es (Async a, Either SomeException a)
waitAnyCatch = unsafeEff_ . A.waitAnyCatch
-- | Lifted 'A.waitAnyCancel'.
waitAnyCancel :: Concurrent :> es => [Async a] -> Eff es (Async a, a)
waitAnyCancel = unsafeEff_ . A.waitAnyCancel
-- | Lifted 'A.waitAnyCatchCancel'.
waitAnyCatchCancel
:: Concurrent :> es
=> [Async a]
-> Eff es (Async a, Either SomeException a)
waitAnyCatchCancel = unsafeEff_ . A.waitAnyCatchCancel
-- | Lifted 'A.waitEither'.
waitEither
:: Concurrent :> es
=> Async a
-> Async b
-> Eff es (Either a b)
waitEither a b = unsafeEff_ $ A.waitEither a b
-- | Lifted 'A.waitEitherCatch'.
waitEitherCatch
:: Concurrent :> es
=> Async a
-> Async b
-> Eff es (Either (Either SomeException a) (Either SomeException b))
waitEitherCatch a b = unsafeEff_ $ A.waitEitherCatch a b
-- | Lifted 'A.waitEitherCancel'.
waitEitherCancel
:: Concurrent :> es
=> Async a
-> Async b
-> Eff es (Either a b)
waitEitherCancel a b = unsafeEff_ $ A.waitEitherCancel a b
-- | Lifted 'A.waitEitherCatchCancel'.
waitEitherCatchCancel
:: Concurrent :> es
=> Async a
-> Async b
-> Eff es (Either (Either SomeException a) (Either SomeException b))
waitEitherCatchCancel a b = unsafeEff_ $ A.waitEitherCatch a b
-- | Lifted 'A.waitEither_'.
waitEither_ :: Concurrent :> es => Async a -> Async b -> Eff es ()
waitEither_ a b = unsafeEff_ $ A.waitEither_ a b
-- | Lifted 'A.waitBoth'.
waitBoth :: Concurrent :> es => Async a -> Async b -> Eff es (a, b)
waitBoth a b = unsafeEff_ $ A.waitBoth a b
-- | Lifted 'A.link'.
link :: Concurrent :> es => Async a -> Eff es ()
link = unsafeEff_ . A.link
-- | Lifted 'A.linkOnly'.
linkOnly :: Concurrent :> es => (SomeException -> Bool) -> Async a -> Eff es ()
linkOnly f = unsafeEff_ . A.linkOnly f
-- | Lifted 'A.link2'.
link2 :: Concurrent :> es => Async a -> Async b -> Eff es ()
link2 a b = unsafeEff_ $ A.link2 a b
-- | Lifted 'A.link2Only'.
link2Only :: Concurrent :> es => (SomeException -> Bool) -> Async a -> Async b -> Eff es ()
link2Only f a b = unsafeEff_ $ A.link2Only f a b
-- | Lifted 'A.race'.
race :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es b -> Eff es (Either a b)
race ma mb = unsafeEff $ \es -> do
A.race (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
-- | Lifted 'A.race_'.
race_ :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es b -> Eff es ()
race_ ma mb = unsafeEff $ \es -> do
A.race_ (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
-- | Lifted 'A.concurrently'.
concurrently :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es b -> Eff es (a, b)
concurrently ma mb = unsafeEff $ \es -> do
A.concurrently (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
-- | Lifted 'A.concurrently_'.
concurrently_ :: (HasCallStack, Concurrent :> es) => Eff es a -> Eff es b -> Eff es ()
concurrently_ ma mb = unsafeEff $ \es -> do
A.concurrently_ (unEff ma =<< cloneEnv es) (unEff mb =<< cloneEnv es)
-- Below functions use variants from the @unliftio@ library as they minimize the
-- amount of spawned threads and are thus much more efficient than the ones from
-- the @async@ library.
-- | Lifted 'A.mapConcurrently'.
mapConcurrently
:: (HasCallStack, Traversable f, Concurrent :> es)
=> (a -> Eff es b)
-> f a
-> Eff es (f b)
mapConcurrently f t = unsafeEff $ \es -> do
U.mapConcurrently (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'A.mapConcurrently_'.
mapConcurrently_
:: (HasCallStack, Foldable f, Concurrent :> es)
=> (a -> Eff es b)
-> f a
-> Eff es ()
mapConcurrently_ f t = unsafeEff $ \es -> do
U.mapConcurrently_ (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'A.forConcurrently'.
forConcurrently
:: (HasCallStack, Traversable f, Concurrent :> es)
=> f a
-> (a -> Eff es b)
-> Eff es (f b)
forConcurrently t f = unsafeEff $ \es -> do
U.forConcurrently t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'A.forConcurrently_'.
forConcurrently_
:: (HasCallStack, Foldable f, Concurrent :> es)
=> f a
-> (a -> Eff es b)
-> Eff es ()
forConcurrently_ t f = unsafeEff $ \es -> do
U.forConcurrently_ t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'A.replicateConcurrently'.
replicateConcurrently
:: (HasCallStack, Concurrent :> es)
=> Int
-> Eff es a
-> Eff es [a]
replicateConcurrently n f = unsafeEff $ \es -> do
U.replicateConcurrently n (unEff f =<< cloneEnv es)
-- | Lifted 'A.replicateConcurrently_'.
replicateConcurrently_
:: (HasCallStack, Concurrent :> es)
=> Int
-> Eff es a
-> Eff es ()
replicateConcurrently_ n f = unsafeEff $ \es -> do
U.replicateConcurrently_ n (unEff f =<< cloneEnv es)
----------------------------------------
-- Pooled concurrency (unliftio)
-- | Lifted 'U.pooledMapConcurrentlyN'.
pooledMapConcurrentlyN
:: (HasCallStack, Concurrent :> es, Traversable t)
=> Int
-> (a -> Eff es b)
-> t a
-> Eff es (t b)
pooledMapConcurrentlyN threads f t = unsafeEff $ \es -> do
U.pooledMapConcurrentlyN threads (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'U.pooledMapConcurrently'.
pooledMapConcurrently
:: (HasCallStack, Concurrent :> es, Traversable t)
=> (a -> Eff es b)
-> t a
-> Eff es (t b)
pooledMapConcurrently f t = unsafeEff $ \es -> do
U.pooledMapConcurrently (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'U.pooledMapConcurrentlyN'.
pooledMapConcurrentlyN_
:: (HasCallStack, Concurrent :> es, Foldable f)
=> Int
-> (a -> Eff es b)
-> f a
-> Eff es ()
pooledMapConcurrentlyN_ threads f t = unsafeEff $ \es -> do
U.pooledMapConcurrentlyN_ threads (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'U.pooledMapConcurrently_'.
pooledMapConcurrently_
:: (HasCallStack, Concurrent :> es, Foldable f)
=> (a -> Eff es b)
-> f a
-> Eff es ()
pooledMapConcurrently_ f t = unsafeEff $ \es -> do
U.pooledMapConcurrently_ (\a -> unEff (f a) =<< cloneEnv es) t
-- | Lifted 'U.pooledForConcurrentlyN'.
pooledForConcurrentlyN
:: (HasCallStack, Concurrent :> es, Traversable t)
=> Int
-> t a
-> (a -> Eff es b)
-> Eff es (t b)
pooledForConcurrentlyN threads t f = unsafeEff $ \es -> do
U.pooledForConcurrentlyN threads t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'U.pooledForConcurrently'.
pooledForConcurrently
:: (HasCallStack, Concurrent :> es, Traversable t)
=> t a
-> (a -> Eff es b)
-> Eff es (t b)
pooledForConcurrently t f = unsafeEff $ \es -> do
U.pooledForConcurrently t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'U.pooledForConcurrentlyN'.
pooledForConcurrentlyN_
:: (HasCallStack, Concurrent :> es, Foldable f)
=> Int
-> f a
-> (a -> Eff es b)
-> Eff es ()
pooledForConcurrentlyN_ threads t f = unsafeEff $ \es -> do
U.pooledForConcurrentlyN_ threads t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'U.pooledForConcurrently_'.
pooledForConcurrently_
:: (HasCallStack, Concurrent :> es, Foldable f)
=> f a
-> (a -> Eff es b)
-> Eff es ()
pooledForConcurrently_ t f = unsafeEff $ \es -> do
U.pooledForConcurrently_ t (\a -> unEff (f a) =<< cloneEnv es)
-- | Lifted 'U.pooledReplicateConcurrentlyN'.
pooledReplicateConcurrentlyN
:: (HasCallStack, Concurrent :> es)
=> Int
-> Int
-> Eff es a
-> Eff es [a]
pooledReplicateConcurrentlyN threads n f = unsafeEff $ \es -> do
U.pooledReplicateConcurrentlyN threads n (unEff f =<< cloneEnv es)
-- | Lifted 'U.pooledReplicateConcurrently'.
pooledReplicateConcurrently
:: (HasCallStack, Concurrent :> es)
=> Int
-> Eff es a
-> Eff es [a]
pooledReplicateConcurrently n f = unsafeEff $ \es -> do
U.pooledReplicateConcurrently n (unEff f =<< cloneEnv es)
-- | Lifted 'U.pooledReplicateConcurrentlyN_'.
pooledReplicateConcurrentlyN_
:: (HasCallStack, Concurrent :> es)
=> Int
-> Int
-> Eff es a
-> Eff es ()
pooledReplicateConcurrentlyN_ threads n f = unsafeEff $ \es -> do
U.pooledReplicateConcurrentlyN_ threads n (unEff f =<< cloneEnv es)
-- | Lifted 'U.pooledReplicateConcurrently_'.
pooledReplicateConcurrently_
:: (HasCallStack, Concurrent :> es)
=> Int
-> Eff es a
-> Eff es ()
pooledReplicateConcurrently_ n f = unsafeEff $ \es -> do
U.pooledReplicateConcurrently_ n (unEff f =<< cloneEnv es)
----------------------------------------
-- Conc
-- | Lifted 'U.Conc'.
data Conc :: [Effect] -> Type -> Type where
Action :: Eff es a -> Conc es a
Apply :: Conc es (v -> a) -> Conc es v -> Conc es a
LiftA2 :: (x -> y -> a) -> Conc es x -> Conc es y -> Conc es a
Pure :: a -> Conc es a
Alt :: Conc es a -> Conc es a -> Conc es a
Empty :: Conc es a
deriving stock instance Functor (Conc es)
instance Applicative (Conc es) where
pure = Pure
(<*>) = Apply
(<*) = LiftA2 (\x _ -> x)
(*>) = LiftA2 (\_ y -> y)
liftA2 = LiftA2
instance Alternative (Conc es) where
empty = Empty
(<|>) = Alt
instance Semigroup a => Semigroup (Conc es a) where
(<>) = liftA2 (<>)
instance Monoid a => Monoid (Conc es a) where
mempty = pure mempty
-- | Lifted 'U.conc'.
conc :: Eff es a -> Conc es a
conc = Action
-- | Lifted 'U.runConc'.
runConc :: (HasCallStack, Concurrent :> es) => Conc es a -> Eff es a
runConc m = unsafeEff $ \es -> U.runConc (unliftConc es m)
where
unliftConc :: Env es -> Conc es a -> U.Conc IO a
unliftConc es = \case
Action f -> I.Action $ unEff f =<< cloneEnv es
Apply a b -> I.Apply (unliftConc es a) (unliftConc es b)
LiftA2 f a b -> I.LiftA2 f (unliftConc es a) (unliftConc es b)
Pure a -> I.Pure a
Alt a b -> I.Alt (unliftConc es a) (unliftConc es b)
Empty -> I.Empty
----------------------------------------
-- Concurrently
-- | Lifted 'A.Concurrently'.
newtype Concurrently es a = Concurrently { runConcurrently :: Eff es a }
instance Functor (Concurrently es) where
fmap f (Concurrently a) = Concurrently (fmap f a)
instance Concurrent :> es => Applicative (Concurrently es) where
pure = Concurrently . pure
Concurrently fs <*> Concurrently as =
Concurrently ((\(f, a) -> f a) <$> concurrently fs as)
instance Concurrent :> es => Alternative (Concurrently es) where
empty = Concurrently . unsafeEff_ . forever $ threadDelay maxBound
Concurrently as <|> Concurrently bs =
Concurrently (either id id <$> race as bs)
instance (Concurrent :> es, Semigroup a) => Semigroup (Concurrently es a) where
(<>) = liftA2 (<>)
instance (Concurrent :> es, Monoid a) => Monoid (Concurrently es a) where
mempty = pure mempty
----------------------------------------
-- Helpers
liftAsync
:: HasCallStack
=> (IO a -> IO (Async a))
-> Eff es a
-> Eff es (Async a)
liftAsync fork action = unsafeEff $ \es -> do
esA <- cloneEnv es
fork $ unEff action esA
liftAsyncWithUnmask
:: HasCallStack
=> (((forall b. IO b -> IO b) -> IO a) -> IO (Async a))
-> ((forall b. Eff es b -> Eff es b) -> Eff es a)
-> Eff es (Async a)
liftAsyncWithUnmask fork action = unsafeEff $ \es -> do
esA <- cloneEnv es
-- Unmask never runs its argument in a different thread.
fork $ \unmask -> unEff (action $ reallyUnsafeLiftMapIO unmask) esA
liftWithAsync
:: HasCallStack
=> (IO a -> (Async a -> IO b) -> IO b)
-> Eff es a
-> (Async a -> Eff es b)
-> Eff es b
liftWithAsync withA action k = unsafeEff $ \es -> do
esA <- cloneEnv es
withA (unEff action esA)
(\a -> unEff (k a) es)
liftWithAsyncWithUnmask
:: HasCallStack
=> (((forall c. IO c -> IO c) -> IO a) -> (Async a -> IO b) -> IO b)
-> ((forall c. Eff es c -> Eff es c) -> Eff es a)
-> (Async a -> Eff es b)
-> Eff es b
liftWithAsyncWithUnmask withA action k = unsafeEff $ \es -> do
esA <- cloneEnv es
-- Unmask never runs its argument in a different thread.
withA (\unmask -> unEff (action $ reallyUnsafeLiftMapIO unmask) esA)
(\a -> unEff (k a) es)