packages feed

A-gent-0.11.0.19: 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
  , tmax
  , ttid
  , tkil
  , tnum
  , tvar
  , tval
  , terr
  )
where

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

{- DEBUG

import           Control.Concurrent
  ( ThreadId
  , forkFinally
  , getNumCapabilities
  , killThread
  , threadDelay
  )

import           Control.Concurrent.MVar
  ( MVar
  , newEmptyMVar
  , putMVar
  , readMVar
  , tryReadMVar
  )

import           GHC.Exception
  ( SomeException
  , errorCallException
  , throw
  )

-}

{-

-}

import           Control.Concurrent
  ( ThreadId
  , forkFinally
  , getNumCapabilities
  , killThread
  )

import           Control.Concurrent.MVar
  ( MVar
  , newEmptyMVar
  , putMVar
  , readMVar
  , tryReadMVar
  )

import           GHC.Exception           ( SomeException, throw )

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

type Wrap a = Either SomeException a
data Task a = Task !ThreadId !(MVar (Wrap a)) !(IO (Wrap a))

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

tmax :: IO Int
tmax =
  getNumCapabilities

ttid
  :: Task a
  -> ThreadId
ttid (Task uid _ _) =
  uid

tkil
  :: Task a
  -> IO String
tkil t@(Task uid _ _) =
  killThread uid >>
  pure n
  where
    n = tnum t

tnum
  :: Task a
  -> String
tnum (Task uid _ _) =
  -- NOTE: Sample of `show uid` is `ThreadId 42` and
  -- > cat /proc/sys/kernel/threads-max
  -- > 509_390
  (lpad 6 '0' . drop 1 . dropWhile (/= ' ') . show) uid
  where
    lpad n c x =
      replicate (n - l) c ++ x
      where
        l = length x

tvar
  :: Task a
  -> IO Bool
tvar (Task _ mvar _) =
    ( \case
        Nothing -> False
        Just __ -> True
    )
    <$> tryReadMVar mvar

tval
  :: Task a
  -> IO (Maybe a)
tval (Task _ _ mval) =
    ( \case
        Right a -> Just a
        Left  _ -> Nothing
    )
    <$> mval

terr
  :: Task a
  -> IO (Maybe String)
terr (Task _ _ mval) =
    ( \case
        Right _ -> Nothing
        Left  e -> Just $ show e
    )
    <$> mval

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

-- |
-- 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 var $ get var
    where
      finally v r =
        -- DEBUG: sleep for 9 million microseconds, or 9 seconds
        --threadDelay 9000000 >>
        -- DEBUG: Force an exception
        --put v (Left $ errorCallException "DEBUG")
        put v r
  wait (Task _ _ mval) =
    ( \case
        Right v ->       v
        Left  e -> throw e
    )
    <$> mval
  join =
    mapM_ wait