Packages

A library for distributed reactive programming with flexible consistency guarantees drawing from QUARP and Rx.. Features the familiar behaviours and event streams in the spirit of FRP.

Current section

Files

Jump to
bquarp lib middleware connector.ex
Raw

lib/middleware/connector.ex

defmodule ReactiveMiddleware.Connector do
use GenServer
alias ReactiveMiddleware.Registry
alias ReactiveMiddleware.Evaluator
require Logger
@port 6666
@multicast {224, 0, 0, 225}
@cookie :hottentottententententoonstelling
####################
# CLIENT INTERFACE #
####################
# Start the server. Called by the app.
def start_link(_arg) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
# Connect manually with a given set of nodes
# (when udp multicast is disfunctional on your machine).
def manual_connect_and_subscribe(ns) do
GenServer.call(__MODULE__, {:connect_all, ns})
end
####################
# SERVER CALLBACKS #
####################
def init([]) do
GenServer.cast(__MODULE__, {:initialize})
{:ok, nil}
end
# Handles the initial request for initialization
# Loops until this node has a fully qualified name (as a result of being networked)
# Then calls the initialize helper function to handle the details.
def handle_cast({:initialize}, nil) do
if Node.self == :nonode@nohost do
#Logger.warn("FQN not materialized yet!")
:timer.sleep(500)
#Logger.warn("Trying again!")
GenServer.cast(__MODULE__, {:initialize})
{:noreply, nil}
else
Node.set_cookie(@cookie)
:rand.seed(:exsplus, :erlang.now)
:timer.sleep(:rand.uniform(1000))
{:ok, s} = initialize()
{:noreply, s}
end
end
# Handles an incoming announcement that is broadcasted by a new node.
def handle_info({:udp, _clientSocket, _clientIp, _clientPort, msg}, s) do
name = String.to_atom(msg)
if name != Node.self do
Logger.info("New node has announced itself: #{name}")
handle_connect(name)
end
{:noreply, s}
end
@doc """
This message is receives when a node returns. (Result of monitoring nodes).
"""
def handle_info({:nodeup, remote}, s) do
Logger.info("Remote node #{inspect remote} back up and running")
handle_connect(remote)
{:noreply, s}
end
@doc """
This message is received when a node disappears from the network.
"""
def handle_info({:nodedown, remote}, s) do
handle_disconnect(remote)
{:noreply, s}
end
# Handles the call for manually connecting and subscribing.
def handle_call({:connect_all, ns}, _from, s) do
ns
|> Enum.each(fn n -> handle_connect(n) end)
{:reply, :ok, s}
end
############################
# IMPLEMENTATION / HELPERS #
############################
# Initializes this nodes network connections automatically.
# - Registers the Registry and Evaluator globally
# - Calls the announce function
# Actually a helper function but can be called manually as well.
defp initialize() do
Logger.info("Starting the Connector")
register()
# Detect node disconnects and reconnects.
:net_kernel.set_net_ticktime(5, 0)
:net_kernel.monitor_nodes(true)
{:ok, s} = open_multicast(@port, @multicast)
announce()
{:ok, s}
end
# Opens a multicast socket that listens to incoming announcements of new nodes.
defp open_multicast(port, addr) do
Logger.debug("Opening a multicast socket")
:gen_udp.open(port, [
:binary,
#Put the following line in comments on a Windows machine.
#{:ip, addr},
{:reuseaddr, true},
{:multicast_ttl, 4},
{:multicast_loop, true},
{:broadcast, true},
{:add_membership, {addr, {0, 0, 0, 0}}},
{:active, true}
])
end
# Registers the Registry and Evaluator globally
defp register() do
Logger.info("Registering the registry and evaluator globally under #{Node.self}")
:global.register_name({Node.self, :registry}, Process.whereis(Registry))
:global.register_name({Node.self, :evaluator}, Process.whereis(Evaluator))
end
# Announces our presence on the network by broadcasting this node's name.
defp announce() do
Logger.info("Announcing our presence on the network")
{:ok, sender} = :gen_udp.open(0, mode: :binary)
:ok = :gen_udp.send(sender, @multicast, @port, "#{Node.self()}")
end
defp handle_disconnect(remote) do
Logger.info("Lost: #{inspect remote}")
Logger.info("Removing its signals from the registry")
Registry.remove_signals_of(remote)
end
defp handle_connect(remote) do
Logger.info("Connecting with: #{inspect remote}")
Node.connect(remote)
:global.sync()
Logger.info("Synchronizing with: #{inspect remote}")
Registry.synchronize(remote)
end
end