Current section
Files
Jump to
Current section
Files
lib/bundlex/cnode/server.ex
defmodule Bundlex.CNode.Server do
@moduledoc false
use GenServer
require Logger
alias Bundlex.CNode.NameStore
@impl true
def init(opts) do
Process.flag(:trap_exit, true)
if opts.link?, do: Process.monitor(opts.caller)
:ok = ensure_node_distributed()
{name, creation} = NameStore.get_name()
cnode = :"#{name}@#{host_name()}"
cookie = Node.get_cookie() |> Atom.to_charlist()
port =
Port.open(
{:spawn_executable, Bundlex.build_path(opts.app, opts.native_name, :cnode)},
args: [host_name(), name, cnode, "#{creation}"],
line: 2048,
env: [{~c"BUNDLEX_ERLANG_COOKIE", cookie}]
)
Process.send_after(self(), :timeout, 5000)
{:ok,
%{
port: port,
status: :waiting,
caller: opts.caller,
link?: opts.link?,
cnode: cnode,
msg_part?: false
}}
catch
err, reason -> {:stop, {err, reason}}
end
@impl true
def handle_info(
{port, {:data, {:eol, ~c"ready"}}},
%{port: port, status: :waiting, msg_part?: false} = state
) do
case Node.connect(state.cnode) do
true ->
send(state.caller, {self(), {:ok, %Bundlex.CNode{server: self(), node: state.cnode}}})
{:noreply, %{state | status: :connected}}
_connect_failed ->
send(state.caller, {self(), {:error, :connect_to_cnode}})
{:stop, :normal, state}
end
end
def handle_info({port, {:data, {flag, data}}}, %{port: port} = state) do
Logger.info("cnode#{inspect(self())}: #{data}")
{:noreply, %{state | msg_part?: flag == :noeol}}
end
def handle_info(:timeout, state) do
case state.status do
:waiting ->
send(state.caller, {self(), {:error, :spawn_cnode}})
{:stop, :normal, state}
_status ->
{:noreply, state}
end
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, %{caller: pid} = state) do
disconnect(state.cnode)
{:stop, :normal, state}
end
def handle_info({:EXIT, port, :normal}, %{port: port} = state) do
{:stop, :normal, state}
end
def handle_info({:EXIT, port, reason}, %{port: port} = state) do
if state.link?, do: Process.exit(state.caller, :shutdown)
{:stop, reason, state}
end
def handle_info({:EXIT, _from, :normal}, state) do
{:noreply, state}
end
def handle_info({:EXIT, _from, reason}, state) do
if state.link?, do: Process.exit(state.caller, reason)
{:stop, reason, state}
end
@impl true
def handle_call(:stop, _from, state) do
{:stop, :normal, disconnect(state.cnode), state}
end
defp ensure_node_distributed(empd_status \\ :unknown) do
if Node.alive?() do
:ok
else
case Node.start(NameStore.get_self_name(), :shortnames) do
{:ok, _pid} ->
Node.set_cookie(:bundlex_cookie)
:ok
{:error, {:already_started, _pid}} ->
# In case the node has been started after the `Node.alive?` check
:ok
{:error, _reason} when empd_status == :unknown ->
Logger.info("Trying to start epmd...")
System.cmd("epmd", ~w(-daemon))
# ensure epmd finished starting
System.cmd("epmd", ~w(-names))
ensure_node_distributed(:start_tried)
{:error, reason} ->
{:error, reason}
end
end
end
defp disconnect(cnode) do
case Node.disconnect(cnode) do
true ->
NameStore.return_name(cnode |> node_name)
:ok
_disconnect_failed ->
{:error, :disconnect_cnode}
end
end
defp node_name(node) do
node |> to_string() |> String.split("@") |> List.first()
end
defp host_name(node \\ Node.self()) do
node |> to_string() |> String.split("@") |> List.last()
end
end