Created
June 3, 2017 20:40
-
-
Save gelisam/848fd5794a045d57fb6e013fe6958be2 to your computer and use it in GitHub Desktop.
A variant of SimpleLocalnet which uses a hardcoded list of nodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env stack | |
-- stack --resolver lts-8.16 script | |
-- for https://www.reddit.com/r/haskell/comments/6emo9g/trying_to_get_the_basic_example_in_cloudhaskell/ | |
{-# LANGUAGE LambdaCase, RecordWildCards #-} | |
import System.Environment (getArgs) | |
import Control.Distributed.Process | |
import Control.Distributed.Process.Node (initRemoteTable) | |
import Control.Distributed.Process.Backend.SimpleLocalnet | |
as SimpleLocalnet hiding (initializeBackend) | |
import Control.Concurrent (threadDelay) | |
import Control.Monad (filterM, when) | |
import Data.IORef | |
import Network.Socket (HostName, ServiceName) | |
import qualified Control.Distributed.Process.Node as Local | |
import qualified Network.Transport.TCP as TCP | |
-- SimpleLocalnet is used to find the other nodes dynamically using | |
-- broadcast packets. If your network blocks those, you can still use | |
-- CloudHaskell by providing this information to your nodes in some | |
-- other way. Here, for simplicity, I am hardcoding it. | |
data HostPort = HostPort | |
{ host :: String | |
, port :: String -- a number encoded as a String | |
} deriving (Show, Eq) | |
hostPortToNode :: HostPort -> NodeId | |
hostPortToNode (HostPort {..}) = NodeId {..} | |
where | |
nodeAddress = TCP.encodeEndPointAddress host port 0 | |
data NodeSettings = NodeSettings | |
{ slaveHostPorts :: [HostPort] | |
, masterHostPort :: HostPort | |
, slaveNodes :: [NodeId] | |
, masterNode :: NodeId | |
} deriving (Show, Eq) | |
hardcodedSettings :: NodeSettings | |
hardcodedSettings = NodeSettings {..} | |
where | |
slaveHostPorts = [ HostPort "localhost" "8080" | |
, HostPort "localhost" "8081" | |
, HostPort "localhost" "8082" | |
, HostPort "localhost" "8083" | |
] | |
masterHostPort = HostPort "localhost" "8084" | |
slaveNodes = map hostPortToNode slaveHostPorts | |
masterNode = hostPortToNode masterHostPort | |
-- And now, we implement a version of initializeBackend which uses | |
-- the hardcoded data instead of a broadcast packet. | |
initializeBackend :: HostName -> ServiceName -> RemoteTable -> IO Backend | |
initializeBackend hostName portNumber remoteTable = do | |
TCP.createTransport hostName | |
portNumber | |
TCP.defaultTCPParameters | |
>>= \case | |
Left err -> fail (show err) | |
Right transport -> do | |
localNode <- Local.newLocalNode transport remoteTable | |
pure $ Backend | |
{ newLocalNode = pure localNode -- not really new, but whatever | |
, findPeers = \microseconds -> do | |
threadDelay microseconds | |
-- for some reason runProcess doesn't return a result | |
-- so we have to store it in a mutable variable | |
ref <- newIORef [] | |
Local.runProcess localNode $ do | |
-- only keep the slave nodes which are already running | |
let allNodes = slaveNodes hardcodedSettings | |
activeNodes <- flip filterM allNodes $ \node -> do | |
getNodeStats node >>= \case | |
Left _ -> pure False | |
Right _ -> pure True | |
liftIO $ writeIORef ref activeNodes | |
readIORef ref | |
, redirectLogsHere = \_ -> pure () -- wharever | |
} | |
-- And now we can run our original SimpleLocalnet-based program from | |
-- https://hackage.haskell.org/package/distributed-process-simplelocalnet-0.2.3.3/docs/Control-Distributed-Process-Backend-SimpleLocalnet.html | |
-- (plus some extra validity checks on the host and port) | |
master :: Backend -> [NodeId] -> Process () | |
master backend slaves = do | |
-- Do something interesting with the slaves | |
liftIO . putStrLn $ "Slaves: " ++ show slaves | |
-- Terminate the slaves when the master terminates (this is optional) | |
terminateAllSlaves backend | |
main :: IO () | |
main = do | |
args <- getArgs | |
case args of | |
["master", host, port] -> do | |
-- validity check | |
let expected = masterHostPort hardcodedSettings | |
actual = HostPort host port | |
when (actual /= expected) $ do | |
fail $ "you requested " ++ show actual ++ ", " | |
++ "but the master must use " ++ show expected ++ ". " | |
++ "please use the hardcoded settings " | |
++ "or change the code to match your needs." | |
backend <- initializeBackend host port initRemoteTable | |
startMaster backend (master backend) | |
["slave", host, port] -> do | |
-- validity check | |
let expected = slaveHostPorts hardcodedSettings | |
actual = HostPort host port | |
when (actual `notElem` expected) $ do | |
fail $ "you requested " ++ show actual ++ ", " | |
++ "but slaves must use one of " ++ show expected ++ ". " | |
++ "please use one of those hardcoded settings " | |
++ "or change the code to match your needs." | |
backend <- initializeBackend host port initRemoteTable | |
startSlave backend |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment