-- Language extensions {{{
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE ViewPatterns #-}
-- }}}
-- Imports {{{
import Control.Monad (forever,replicateM_)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Concurrent (forkIO,threadDelay)
import Control.Concurrent.STM (atomically,modifyTVar,newTVarIO,readTVar,writeTVar)
import Data.Functor ((<$>))
import Data.IORef (modifyIORef,newIORef,readIORef)
import Data.Monoid ((<>),mempty)
import GHC.Conc (unsafeIOToSTM)
import Network (PortID(..))
import qualified System.Log.Logger as Logger
import System.Log.Logger (Priority(DEBUG,INFO),rootLoggerName,setLevel,updateGlobalLogger)
import System.Log.Logger.TH
import System.Random (randomRIO)
import Test.Framework
import Test.Framework.Providers.HUnit
import Test.HUnit hiding (Test,Path)
import LogicGrowsOnTrees
import LogicGrowsOnTrees.Checkpoint
import LogicGrowsOnTrees.Examples.Queens
import LogicGrowsOnTrees.Parallel.Adapter.Network
import LogicGrowsOnTrees.Parallel.ExplorationMode
import LogicGrowsOnTrees.Parallel.Purity (Purity(Pure))
-- }}}
-- Logging Functions {{{
deriveLoggers "Logger" [DEBUG,INFO]
-- }}}
-- Helper Functions {{{
remdups :: (Eq a) => [a] -> [a] -- {{{
remdups [] = []
remdups (x : []) = [x]
remdups (x : xx : xs)
| x == xx = remdups (x : xs)
| otherwise = x : remdups (xx : xs)
-- }}}
-- }}}
main = do
-- updateGlobalLogger rootLoggerName (setLevel DEBUG)
defaultMain tests
tests = -- {{{
[testCase "one process" . runTest $ \changeNumberOfWorkers → do
changeNumberOfWorkers (const 0)
changeNumberOfWorkers (const 1)
,testCase "two processes" . runTest $ \changeNumberOfWorkers → do
changeNumberOfWorkers (3-)
,testCase "many processes" . runTest $ \changeNumberOfWorkers → liftIO (randomRIO (0,1::Int)) >>= \i → case i of
0 → changeNumberOfWorkers (\i → if i > 1 then i-1 else i)
1 → changeNumberOfWorkers (+1)
]
where
runTest generateNoise = do
let tree = nqueensCount 15
port_id = PortNumber 54210
progresses_ref ← newIORef []
worker_ids_var ← newTVarIO []
let changeNumberOfWorkers computeNewNumberOfWorkers = do
old_number_of_workers ← liftIO . atomically $ length <$> readTVar worker_ids_var
let new_number_of_workers = computeNewNumberOfWorkers old_number_of_workers
case new_number_of_workers `compare` old_number_of_workers of
EQ → return ()
GT → liftIO
.
replicateM_ (new_number_of_workers-old_number_of_workers)
.
forkIO
.
unsafeRunNetwork
$
runWorker
AllMode
Pure
tree
"localhost"
port_id
LT → replicateM_ (old_number_of_workers-new_number_of_workers) $
(liftIO . atomically $ do
worker_ids ← readTVar worker_ids_var
let number_of_workers = length worker_ids
if number_of_workers > 0
then do
index_to_remove ← unsafeIOToSTM $ randomRIO (0,number_of_workers-1)
writeTVar worker_ids_var $ take index_to_remove worker_ids ++ drop (number_of_workers+1) worker_ids
return . Just $ worker_ids !! index_to_remove
-- Because there is a delay between when a
-- disconnect request is made and when it is
-- processed, sometimes the number of workers
-- will decrease behind our back.
else return Nothing
) >>= maybe (return ()) disconnectWorker
notifyConnected worker_id = do
atomically $ modifyTVar worker_ids_var (worker_id:)
return True
notifyDisconnected _ = return ()
RunOutcome _ termination_reason ←
unsafeRunNetwork $
runSupervisor
AllMode
(const $ return ())
NetworkCallbacks{..}
port_id
mempty
(do changeNumberOfWorkers (const 1)
forever $ do
liftIO $ threadDelay 1000
requestProgressUpdate >>= liftIO . modifyIORef progresses_ref . (:)
generateNoise changeNumberOfWorkers
)
result ← case termination_reason of
Aborted _ → error "prematurely aborted"
Completed result → return result
Failure _ message → error message
let correct_result = exploreTree tree
result @?= correct_result
progresses ← remdups <$> readIORef progresses_ref
replicateM_ 4 $ randomRIO (0,length progresses-1) >>= \i → do
let Progress checkpoint result = progresses !! i
result @=? exploreTreeStartingFromCheckpoint (invertCheckpoint checkpoint) tree
correct_result @=? result <> (exploreTreeStartingFromCheckpoint checkpoint tree)
-- }}}