-- | Functions and types for safely working with 'Async's in 'Scoped' blocks
module Control.Monad.Scoped.Async
  ( -- * Scoped 'Control.Concurrent.Async.Async'
    ScopedAsync

    -- * Allocating a new 'ScopedAsync' in a 'Scoped' block
  , async
  , asyncBound

    -- * Waiting for a 'ScopedAsync' to finish
  , wait
  , waitCatch

    -- * Waiting for a 'ScopedAsync' to finish as part of the handlers of the 'Scoped' block
  , waitScoped
  , waitCatchScoped
  )
where

import Control.Concurrent.Async (Async)
import Control.Exception (SomeException)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Scoped.Internal (Scoped (UnsafeMkScoped), ScopedResource (UnsafeMkScopedResource, unsafeUnwrapScopedResource), registerHandler)
import UnliftIO (MonadUnliftIO)
import UnliftIO.Async (withAsync, withAsyncBound)
import UnliftIO.Async qualified as Async

-- | Just like 'Async' but bound to a 'Scoped' block
type ScopedAsync s a = ScopedResource s (Async a)

-- | Run an 'IO' action asynchronously in a Scoped block. When the 'Scoped' block ends, the 'Async' is 'Control.Concurrent.Async.cancel'led
async :: MonadUnliftIO m => m a -> Scoped s m (ScopedAsync s a)
async :: forall {k} (m :: Type -> Type) a (s :: k).
MonadUnliftIO m =>
m a -> Scoped s m (ScopedAsync s a)
async m a
act = (forall b. (ScopedAsync s a -> m b) -> m b)
-> Scoped s m (ScopedAsync s a)
forall {l} {k} (s :: l) (m :: k -> Type) a.
(forall (b :: k). (a -> m b) -> m b) -> Scoped s m a
UnsafeMkScoped \ScopedAsync s a -> m b
k -> m a -> (Async a -> m b) -> m b
forall (m :: Type -> Type) a b.
MonadUnliftIO m =>
m a -> (Async a -> m b) -> m b
withAsync m a
act (ScopedAsync s a -> m b
k (ScopedAsync s a -> m b)
-> (Async a -> ScopedAsync s a) -> Async a -> m b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Async a -> ScopedAsync s a
forall {k} (s :: k) a. a -> ScopedResource s a
UnsafeMkScopedResource)

-- | Like 'async' but uses 'Control.Concurrent.forkOS' internally
asyncBound :: MonadUnliftIO m => m a -> Scoped s m (ScopedAsync s a)
asyncBound :: forall {k} (m :: Type -> Type) a (s :: k).
MonadUnliftIO m =>
m a -> Scoped s m (ScopedAsync s a)
asyncBound m a
act = (forall b. (ScopedAsync s a -> m b) -> m b)
-> Scoped s m (ScopedAsync s a)
forall {l} {k} (s :: l) (m :: k -> Type) a.
(forall (b :: k). (a -> m b) -> m b) -> Scoped s m a
UnsafeMkScoped \ScopedAsync s a -> m b
k -> m a -> (Async a -> m b) -> m b
forall (m :: Type -> Type) a b.
MonadUnliftIO m =>
m a -> (Async a -> m b) -> m b
withAsyncBound m a
act (ScopedAsync s a -> m b
k (ScopedAsync s a -> m b)
-> (Async a -> ScopedAsync s a) -> Async a -> m b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Async a -> ScopedAsync s a
forall {k} (s :: k) a. a -> ScopedResource s a
UnsafeMkScopedResource)

-- | Wait for the 'ScopedAsync' to finish immediately
wait :: MonadIO m => ScopedAsync s a -> Scoped s m a
wait :: forall {l} (m :: Type -> Type) (s :: l) a.
MonadIO m =>
ScopedAsync s a -> Scoped s m a
wait = Async a -> Scoped s m a
forall (m :: Type -> Type) a. MonadIO m => Async a -> m a
Async.wait (Async a -> Scoped s m a)
-> (ScopedAsync s a -> Async a) -> ScopedAsync s a -> Scoped s m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ScopedAsync s a -> Async a
forall {k} (s :: k) a. ScopedResource s a -> a
unsafeUnwrapScopedResource

-- | Like 'wait' but return either @'Left' 'SomeException'@ or @'Right' a@
waitCatch :: MonadIO m => ScopedAsync s a -> Scoped s m (Either SomeException a)
waitCatch :: forall {l} (m :: Type -> Type) (s :: l) a.
MonadIO m =>
ScopedAsync s a -> Scoped s m (Either SomeException a)
waitCatch = Async a -> Scoped s m (Either SomeException a)
forall (m :: Type -> Type) a.
MonadIO m =>
Async a -> m (Either SomeException a)
Async.waitCatch (Async a -> Scoped s m (Either SomeException a))
-> (ScopedAsync s a -> Async a)
-> ScopedAsync s a
-> Scoped s m (Either SomeException a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ScopedAsync s a -> Async a
forall {k} (s :: k) a. ScopedResource s a -> a
unsafeUnwrapScopedResource

-- | Like 'wait' but wait as part of the handlers of the 'Scoped' block
waitScoped :: MonadUnliftIO m => ScopedAsync s a -> Scoped s m ()
waitScoped :: forall {l} (m :: Type -> Type) (s :: l) a.
MonadUnliftIO m =>
ScopedAsync s a -> Scoped s m ()
waitScoped ScopedAsync s a
a = m a -> Scoped s m ()
forall {l} (m :: Type -> Type) a (s :: l).
MonadUnliftIO m =>
m a -> Scoped s m ()
registerHandler (Async a -> m a
forall (m :: Type -> Type) a. MonadIO m => Async a -> m a
Async.wait (Async a -> m a) -> Async a -> m a
forall a b. (a -> b) -> a -> b
$ ScopedAsync s a -> Async a
forall {k} (s :: k) a. ScopedResource s a -> a
unsafeUnwrapScopedResource ScopedAsync s a
a)

-- | Like 'waitCatch' but wait as part of the handlers of the 'Scoped' block
waitCatchScoped :: MonadUnliftIO m => ScopedAsync s a -> Scoped s m ()
waitCatchScoped :: forall {l} (m :: Type -> Type) (s :: l) a.
MonadUnliftIO m =>
ScopedAsync s a -> Scoped s m ()
waitCatchScoped ScopedAsync s a
a = m (Either SomeException a) -> Scoped s m ()
forall {l} (m :: Type -> Type) a (s :: l).
MonadUnliftIO m =>
m a -> Scoped s m ()
registerHandler (Async a -> m (Either SomeException a)
forall (m :: Type -> Type) a.
MonadIO m =>
Async a -> m (Either SomeException a)
Async.waitCatch (Async a -> m (Either SomeException a))
-> Async a -> m (Either SomeException a)
forall a b. (a -> b) -> a -> b
$ ScopedAsync s a -> Async a
forall {k} (s :: k) a. ScopedResource s a -> a
unsafeUnwrapScopedResource ScopedAsync s a
a)