
module Control.Concurrent.Parallel(parallel_) where

import GHC.Conc(numCapabilities)
import Control.Concurrent
import Control.Concurrent.Chan
import Control.Concurrent.MVar
import Control.Exception
import Control.Monad
import System.IO.Unsafe


{-# NOINLINE addParallel #-}
addParallel :: IO a -> IO ()
addParallel = unsafePerformIO $ do
    chan <- newChan
    mainThread <- myThreadId
    let err = throwTo mainThread $ ErrorCall "Control.Concurrent.Parallel: parallel thread died."
    replicateM_ (numCapabilities-1) $ forkIO $ finally (forever $ join $ readChan chan) err
    return $ writeChan chan

-- | Run the list of computations in parallel
--   Rule: No thread should get pre-empted (although not a guarantee)
parallel_ :: [IO a] -> IO ()
parallel_ xs | numCapabilities <= 1 = sequence_ xs
parallel_ [] = return ()
parallel_ [x] = x >> return ()
parallel_ (x:xs) = do
    ys <- mapM idempotent xs
    mapM_ addParallel ys
    sequence_ $ x : reverse ys


-- RULES:
-- The evaluation of act will happen at most once
-- If the resultant computation returns, the result will have happened exactly once
idempotent :: IO a -> IO (IO a)
idempotent act = do
    mvar <- newMVar Nothing
    return $ modifyMVar mvar f
    where
        f (Just x) = return (Just x, x)
        f Nothing = do
            v <- act
            return (Just v, v)
