packages feed

duoids-0.0.1.0: src/Control/Duoidal/Async.hs

{-# LANGUAGE Safe #-}

-- |
-- Copyright:
--   2012 Simon Marlow
--   2024 Greg Pfeil
-- License: BSD-3-Clause
-- Stability: provisional
-- Portability: non-portable (requires concurrency)
--
-- This largely contains code copied from
-- "async:Control.Concurrent.Async.Internal", and thus inherits the copyright
-- from that module.
--
-- It’s copied rather than depended on to avoid the dependencies on
-- [hashable](https://hackage.haskell.org/package/hashable) and
-- [stm](https://hackage.haskell.org/package/stm).
module Control.Duoidal.Async
  ( liftA2,
    empty,
    (<|>),
  )
where

import "base" Control.Applicative (pure)
import "base" Control.Category (id, (.))
import "base" Control.Concurrent
  ( forkIO,
    newEmptyMVar,
    putMVar,
    takeMVar,
    threadDelay,
  )
import "base" Control.Exception
  ( BlockedIndefinitelyOnMVar (BlockedIndefinitelyOnMVar),
    Exception,
    SomeException,
    asyncExceptionFromException,
    asyncExceptionToException,
    catch,
    fromException,
    mask,
    onException,
    throwIO,
    throwTo,
    toException,
    uninterruptibleMask_,
  )
import "base" Control.Monad (forever, replicateM_, return, when, (=<<), (>>=))
import "base" Data.Either (Either (Left, Right), either)
import "base" Data.Eq (Eq)
import "base" Data.Function (($))
import "base" Data.Functor (void, (<$>))
import "base" Data.IORef (modifyIORef, newIORef, readIORef)
import "base" Data.Int (Int)
import "base" Data.Kind (Type)
import "base" Data.Ord ((>))
import "base" System.IO (IO)
import "base" Text.Show (Show)
import "base" Prelude (maxBound, subtract)

concurrently' ::
  IO a ->
  IO b ->
  (IO (Either SomeException (Either a b)) -> IO r) ->
  IO r
concurrently' left right collect = do
  done <- newEmptyMVar
  mask $ \restore -> do
    -- Note: uninterruptibleMask here is because we must not allow
    -- the putMVar in the exception handler to be interrupted,
    -- otherwise the parent thread will deadlock when it waits for
    -- the thread to terminate.
    lid <-
      forkIO
        . uninterruptibleMask_
        $ restore (left >>= putMVar done . Right . Left)
          `catch` (putMVar done . Left)
    rid <-
      forkIO
        . uninterruptibleMask_
        $ restore (right >>= putMVar done . Right . Right)
          `catch` (putMVar done . Left)

    count <- newIORef (2 :: Int)
    let takeDone = do
          r <- takeMVar done -- interruptible
          -- Decrement the counter so we know how many takes are left.
          -- Since only the parent thread is calling this, we can
          -- use non-atomic modifications.
          -- NB. do this *after* takeMVar, because takeMVar might be
          -- interrupted.
          modifyIORef count (subtract 1)
          pure r

    let tryAgain f = f `catch` \BlockedIndefinitelyOnMVar -> f

        stop = do
          -- kill right before left, to match the semantics of
          -- the version using withAsync. (#27)
          uninterruptibleMask_ $ do
            count' <- readIORef count
            -- we only need to use killThread if there are still
            -- children alive.  Note: forkIO here is because the
            -- child thread could be in an uninterruptible
            -- putMVar.
            when (count' > 0) . void . forkIO $ do
              throwTo rid AsyncCancelled
              throwTo lid AsyncCancelled
            -- ensure the children are really dead
            replicateM_ count' (tryAgain $ takeMVar done)

    r <- collect (tryAgain takeDone) `onException` stop
    stop
    pure r

-- | The exception thrown by `cancel` to terminate a thread.
type AsyncCancelled :: Type
data AsyncCancelled = AsyncCancelled
  deriving stock (Show, Eq)

instance Exception AsyncCancelled where
  fromException = asyncExceptionFromException
  toException = asyncExceptionToException

-- | Run two @IO@ actions concurrently, and return the first to
-- finish.  The loser of the race is 'cancel'led.
--
-- > race left right =
-- >   withAsync left $ \a ->
-- >   withAsync right $ \b ->
-- >   waitEither a b
race :: IO a -> IO b -> IO (Either a b)
race left right = concurrently' left right (either throwIO return =<<)

liftA2 :: (a -> b -> c) -> IO a -> IO b -> IO c
liftA2 f left right = concurrently' left right (collect [])
  where
    collect [Left a, Right b] _ = pure $ f a b
    collect [Right b, Left a] _ = pure $ f a b
    collect xs m = do
      e <- m
      case e of
        Left ex -> throwIO ex
        Right r -> collect (r : xs) m

empty :: IO a
empty = forever $ threadDelay maxBound

(<|>) :: IO a -> IO a -> IO a
a <|> b = either id id <$> race a b

infixl 3 <|>