packages feed

A-gent-0.11.0.6: src/Agent/Control/Concurrent.hs

{-# OPTIONS_GHC -Wall -Werror #-}

{-# LANGUAGE NoGeneralizedNewtypeDeriving #-}
{-# LANGUAGE Safe                         #-}

{-# LANGUAGE LambdaCase                   #-}

--------------------------------------------------------------------------------

-- |
-- Copyright  : (c) 2026 SPISE MISU ApS
-- License    : SSPL-1.0 OR AGPL-3.0-only
-- Maintainer : SPISE MISU <mail+hackage@spisemisu.com>
-- Stability  : experimental

--------------------------------------------------------------------------------

module Agent.Control.Concurrent
  ( Async
  , Task
  , fork
  , wait
  , join
  )
where

--------------------------------------------------------------------------------

import           Control.Concurrent      ( ThreadId, forkFinally )
import           Control.Concurrent.MVar
  ( MVar
  , newEmptyMVar
  , putMVar
  , readMVar
  )
import           GHC.Exception           ( SomeException, throw )

--------------------------------------------------------------------------------

data Task a = Task !ThreadId (IO (Either SomeException a))

--------------------------------------------------------------------------------

-- |
-- To prevent the users from adding instances of `Async`, we
-- provide a middle-layer (`Proxy`) between the `Monad` instance and the `Proxy`
-- (`Async`) instance.

class Monad m => Proxy m where
  new :: m (MVar a)
  put ::    MVar a -> a -> m ( )
  get ::    MVar a ->      m  a
class Proxy m => Async m where
  fork ::   m    a   -> m (Task a)
  wait ::   Task a   -> m       a
  join :: [ Task a ] -> m      ( )

--------------------------------------------------------------------------------

instance Proxy IO where
  -- | newEmptyMVar: Create an MVar which is initially empty.
  new = newEmptyMVar
  -- | putMVar: Put a value into an MVar.
  put = putMVar
  -- |
  -- readMVar: Atomically read the contents of an MVar. If the MVar is currently
  -- empty, readMVar will wait until it is full. readMVar is guaranteed to
  -- receive the next putMVar.
  get = readMVar

instance Async IO where
  fork compute =
    new                               >>= \ var ->
    forkFinally compute (finally var) >>= \ tid ->
    pure $ Task tid $ get var
    where
      finally v r = put v r
  wait (Task _ mvar) =
    ( \case
        Right v ->       v
        Left  e -> throw e
    )
    <$> mvar
  join =
    mapM_ wait