packages feed

unfork-1.0.0.0: Unfork/Async/Core.hs

{-

    This module constitutes the main contribution of the library

-}

module Unfork.Async.Core where

import Prelude (Eq ((==)), IO, pure)

import Control.Applicative ((<|>))
import Control.Concurrent.Async (concurrently)
import Control.Monad (guard, join)
import Control.Monad.STM (STM, atomically)
import Data.Functor (($>), (<&>))

import qualified Control.Concurrent.STM as STM

data Unfork a c = forall q. Unfork
  { unforkedAction ::         --   The unforked action that we give
      !( Ctx q -> a -> c )    --  to the user-provided continuation
  , executeOneTask ::
      !( q -> IO () )   -- How the queue worker processes each item
  }

data Ctx q = Ctx          -- Mutable context for an async unforking
  { queue :: !(STM.TQueue q)
  , stopper :: !(STM.TVar Status)
  }

data Status = Stop | Go          deriving Eq

enqueue :: Ctx q -> q -> STM ()              --  Write to the queue
enqueue Ctx{ queue } = STM.writeTQueue queue

next :: Ctx q -> STM q                      --  Read from the queue
next Ctx{ queue } = STM.readTQueue queue

stop :: Ctx q -> IO ()   --  Indicate to the queue loop thread that
stop Ctx{ stopper } =    --  it should stop once all tasks are done
    atomically (STM.writeTVar stopper Stop)

checkStopped :: Ctx q -> STM ()     --     STM action that succeeds
checkStopped Ctx{ stopper } = do    --  only if 'stop' has been run
    s <- STM.readTVar stopper
    guard (s == Stop)

unforkAsync ::                   --        This is the basis of all
    Unfork a c                   --  four async unforking functions
    -> ((a -> c) -> IO b)
    -> IO b
unforkAsync Unfork{ unforkedAction, executeOneTask } continue =
  do
    ctx <- do
        queue <- STM.newTQueueIO
        stopper <- STM.newTVarIO Go
        pure Ctx{ queue, stopper }

    let
      loop = join (atomically (act <|> done))
        where
          act = next ctx <&> \x -> do{ executeOneTask x; loop }
          done = checkStopped ctx $> pure ()

    ((), c) <- concurrently loop do
        x <- continue (unforkedAction ctx)
        stop ctx
        pure x

    pure c