packages feed

hgraph-1.10.0.0: src/HGraph/Parallel.hs

{-# Language CPP #-}

module HGraph.Parallel
       ( produceConsumeExhaustive
       , processJobList
       )
where

import HGraph.Debugging

import Control.Concurrent
import Data.Maybe
import Control.Monad
import Control.Concurrent.MVar
import Control.Exception
import System.IO (hPutStrLn, stderr)

-- | Runs the producer until it output `Nothing`.
-- Each successful product is sent to the consumer.
-- Returns an IO action which takes the next product (or `Nothing` if there are no more products)
produceConsumeExhaustive :: (IO (Maybe a)) -> (a -> IO b) -> IO (IO (Maybe b))
produceConsumeExhaustive produce consume = do
  numForks <- getNumCapabilities
  mProduct <- newEmptyMVar
  workerDone <- replicateM numForks $ newEmptyMVar
  producers <- forM workerDone $ \doneM -> forkFinally
    ( let loop = do
              g <- hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ produce
              when (isJust g) $ do
                hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mProduct g
                loop
      in loop
    )
    (\e -> do
      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar doneM ()
      case e of
        Left err -> (sayErrString (__FILE__ ++ " on line " ++ show __LINE__
                                   ++ " produce-consume error (producer):\n\t -"
                                   ++ show (err :: SomeException)))
        Right res -> do
          return ()
    )
  consumerDone <- replicateM numForks $ newEmptyMVar
  consumerOutput <- newEmptyMVar
  consumers <- forM consumerDone $ \doneM -> forkFinally
    ( let loop = do
                  g <- hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ takeMVar mProduct
                  case g of
                    Just p -> do
                      out <- hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ consume p
                      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar consumerOutput (Just out)
                      loop
                    Nothing -> hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mProduct Nothing
      in loop
    )
    (\e -> do
      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar doneM ()
      case e of
        Left err -> (sayErrString (__FILE__ ++ " on line " ++ show __LINE__ ++ ": produce-consume error (consumer):\n\t- " ++ show (err :: SomeException)))
        Right res -> do
          return ()
    )
  forkFinally (do
    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ mapM_ takeMVar workerDone
    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mProduct Nothing
    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ mapM_ takeMVar consumerDone
    )
    (\res -> do
      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar consumerOutput Nothing
      case res of
        Left err -> sayErrString (__FILE__ ++ " on line " ++ show __LINE__
                                  ++ ": produce-consume error (final):\n\t- "
                                  ++ show (err :: SomeException))
        Right _ -> return ()
    )

  return (takeMVar consumerOutput)

processJobList :: (a -> IO ([a], [b])) -> [a] -> IO (IO (Maybe b))
processJobList worker jobs = do
  numForks <- getNumCapabilities
  workerDone <- replicateM numForks $ newEmptyMVar
  workerOutput <- newEmptyMVar
  mReady <- newMVar ()
  mJobs  <- newMVar jobs
  mBusyCount <- newMVar 0

  _ <- forM workerDone $ \doneM -> forkFinally
    ( let loop = do
              hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ takeMVar mReady
              openJobs <- hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ takeMVar mJobs
              busy0 <- modifyMVar mBusyCount (\c -> return $ (c + 1, c + 1))
              case openJobs of
                [] -> do
                  modifyMVar_ mBusyCount (\c -> return $ c - 1)
                  if busy0 == 1 then do
                    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mReady ()
                    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mJobs []
                  else
                    loop
                (j : jobs') -> do
                  when (not $ null jobs') $ 
                    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mReady ()
                  hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mJobs jobs'
                  (newJobs, newResults) <- worker j
                  openJobs'' <- modifyMVar mJobs $ \openJobs' -> do
                    return $ (newJobs ++ openJobs', openJobs')
                  busy <- modifyMVar mBusyCount (\c -> return $ (c - 1, c - 1))
                  when (null openJobs'' && ( (not $ null newJobs) || busy == 0)) $ do
                    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar mReady ()
                  hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ mapM_ ((putMVar workerOutput) . Just) newResults
                  loop
      in loop
    )
    (\e -> do
      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar doneM ()
      case e of
        Left err -> (sayErrString (__FILE__ ++ " on line " ++ show __LINE__
                                   ++ " job list error (worker):\n\t -"
                                   ++ show (err :: SomeException)))
        Right res -> do
          return ()
    )

  forkFinally (do
    hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ mapM_ takeMVar workerDone
    )
    (\res -> do
      hasLocked (__FILE__ ++ " on line " ++ show __LINE__) $ putMVar workerOutput Nothing
      case res of
        Left err -> sayErrString (__FILE__ ++ " on line " ++ show __LINE__
                                  ++ ": job list error (end):\n\t- "
                                  ++ show (err :: SomeException))
        Right _ -> return ()
    )

  return (takeMVar workerOutput)