Packages

Attention: This project has been renamed to nodex

Retired package: Deprecated

Current section

Files

Jump to
cnodex lib cnodex.ex
Raw

lib/cnodex.ex

defmodule Cnodex do
@moduledoc """
**Attention:** This project has been renamed to [nodex](https://hex.pm/packages/nodex).
[Nodex Documentation](https://hexdocs.pm/nodex/api-reference.html).
"""
use GenServer
require Logger
def start_link(init_args, opts \\ []) do
GenServer.start_link(__MODULE__, init_args, opts)
end
def init(args) do
unless Node.alive? do
raise "Node is not alive. Cannot connect to a cnode."
end
sname = Map.get_lazy(args, :sname, &random_sname/0)
hostname = Map.get_lazy(args, :hostname, &node_hostname/0)
cnode = :"#{sname}@#{hostname}"
state = %{
exec_path: Map.fetch!(args, :exec_path),
sname: sname,
hostname: hostname,
cnode: cnode,
ready_line: Map.get(args, :ready_line, "#{cnode} ready"),
spawn_inactive_timeout: Map.get(args, :spawn_inactive_timeout, 5000),
os_pid: Map.get(args, :os_pid, nil)
}
init_cnode(state)
end
def random_sname, do: :crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower)
def node_hostname, do: Node.self() |> to_string |> String.split("@") |> List.last
def node_sname, do: Node.self() |> to_string |> String.split("@") |> List.first
def init_cnode(%{cnode: cnode} = state) do
case establish_connection(state) do
{:ok, state} -> {:ok, state}
_ ->
Logger.debug("connection to #{cnode} failed, spawning")
spawn_cnode(state)
end
end
def establish_connection(%{cnode: cnode} = state) do
if Node.connect(cnode) do
Logger.debug("connected to #{cnode}")
Node.monitor(cnode, true)
{:ok, state}
else
{:stop, :unable_to_establish_connection}
end
end
def spawn_cnode(%{
exec_path: exec_path,
sname: sname,
hostname: hostname
} = state) do
cookie = :erlang.get_cookie()
tname = node_sname()
port = Port.open({:spawn_executable, exec_path}, [
:binary,
:exit_status,
:stderr_to_stdout,
line: 4096,
args: [sname, hostname, cookie, tname],
])
os_pid = Keyword.get(Port.info(port), :os_pid)
state = Map.put(state, :os_pid, os_pid)
await_cnode_ready(port, state)
end
def await_cnode_ready(port, %{
ready_line: ready_line
} = state) do
spawn_inactive_timeout = Map.get(state, :spawn_inactive_timeout, 5000)
receive do
{^port, {:data, {:eol, ^ready_line}}} ->
establish_connection(state)
{^port, {:data, {:eol, line}}} ->
Logger.debug("c-node is saying: #{line}")
await_cnode_ready(port, state)
{^port, {:exit_status, exit_status}} ->
Logger.debug("unexpected c-node exit: #{exit_status}")
{:stop, :cnode_unexpected_exit}
m ->
IO.inspect(m)
raise "unhandled msg while waiting for cnode ready"
after
spawn_inactive_timeout ->
{:stop, :spawn_inactive_timeout}
end
end
def handle_info({:nodedown, _cnode}, state) do
{:stop, :nodedown, state}
end
def handle_info(msg, state) do
Logger.warn "unhandled handle_info: #{inspect msg}"
{:noreply, state}
end
def handle_call(:cnode, _from, %{cnode: cnode} = state) do
{:reply, {:ok, cnode}, state}
end
def terminate(_reason, %{os_pid: os_pid}) when os_pid != nil do
System.cmd("kill", ["-9", os_pid])
:normal
end
def cnode(pid_or_name) do
{:ok, cnode} = GenServer.call(pid_or_name, :cnode)
cnode
end
def call(pid_or_name, msg, timeout \\ 5000) do
node = cnode(pid_or_name)
send({nil, node}, msg)
await_response(timeout)
end
defp await_response(:infinite) do
receive do
response -> {:ok, response}
end
end
defp await_response(timeout) when is_integer(timeout) do
receive do
response -> {:ok, response}
after
timeout -> {:error, :timeout}
end
end
end