Skip to content

Instantly share code, notes, and snippets.

@gelisam
Created June 3, 2017 20:40
Show Gist options
  • Save gelisam/848fd5794a045d57fb6e013fe6958be2 to your computer and use it in GitHub Desktop.
Save gelisam/848fd5794a045d57fb6e013fe6958be2 to your computer and use it in GitHub Desktop.
A variant of SimpleLocalnet which uses a hardcoded list of nodes
#!/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