Packages
livebook
0.16.4
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/runtime/epmd.ex
defmodule Livebook.Runtime.EPMD do
# A custom EPMD module used to bypass the epmd OS daemon in the
# standalone runtime.
#
# We used to start epmd on application boot, however sometimes it
# would fail. In particular, on Windows starting epmd may require
# accepting a firewall pop up, and the first boot could still fail.
# To avoid this, we use a custom port resolution that does not rely
# on the epmd OS daemon running.
# From Erlang/OTP 23+
@epmd_dist_version 6
@doc """
Persists parent information, used when connecting to the parent.
"""
def register_parent(parent_node, parent_port) do
[name, host] = parent_node |> Atom.to_charlist() |> :string.split(~c"@")
:persistent_term.put(:livebook_parent, {name, host, parent_node, parent_port})
end
@doc """
Returns the current distribution port.
"""
def dist_port do
:persistent_term.get(:livebook_dist_port)
end
# Custom EPMD callbacks
# Custom callback to register our current node port.
def register_node(name, port), do: register_node(name, port, :inet)
def register_node(name, port, family) do
:persistent_term.put(:livebook_dist_port, port)
:persistent_term.put(:livebook_dist_family, family)
case :erl_epmd.register_node(name, port, family) do
{:ok, creation} -> {:ok, creation}
{:error, :already_registered} -> {:error, :already_registered}
# If registration fails because EPMD is not running, we ignore
# that, because we do not rely on EPMD
_ -> {:ok, -1}
end
end
# Custom callback for resolving parent and sibling node ports.
def port_please(name, host), do: port_please(name, host, :infinity)
def port_please(name, host, timeout) do
# If the target node is on the same host, check if it's a Livebook
# server or runtime to bypass EPMD. If it is a different node, we
# always fall back to :erl_epmd, even if it's a Livebook node.
if host_to_ip!(host()) == host_to_ip!(host) do
case livebook_port(name) do
0 -> :erl_epmd.port_please(name, host, timeout)
port -> {:port, port, @epmd_dist_version}
end
else
:erl_epmd.port_please(name, host, timeout)
end
end
defp host() do
[_, host] = node() |> Atom.to_charlist() |> :string.split(~c"@")
host
end
import Record
defrecordp :hostent, Record.extract(:hostent, from_lib: "kernel/include/inet.hrl")
defp host_to_ip!(host) when is_tuple(host), do: host
defp host_to_ip!(host) when is_atom(host), do: host_to_ip!(Atom.to_charlist(host))
defp host_to_ip!(host) when is_list(host) do
family = :persistent_term.get(:livebook_dist_family)
case :inet.gethostbyname(host, family) do
{:ok, hostent(h_addrtype: ^family, h_addr_list: [ip | _])} ->
ip
other ->
raise "failed to resolve hostname #{inspect(host)}, reason: #{inspect(other)}"
end
end
defp livebook_port(name) do
{parent_name, parent_host, parent_node, parent_port} = :persistent_term.get(:livebook_parent)
case match_name(name, parent_name) do
:parent -> parent_port
:sibling -> sibling_port(parent_node, name, parent_host)
:none -> 0
end
end
defp match_name([x | name], [x | parent_name]), do: match_name(name, parent_name)
defp match_name([?-, ?- | _name], _parent), do: :sibling
defp match_name([], []), do: :parent
defp match_name(_name, _parent), do: :none
defp sibling_port(parent_node, name, host) do
:gen_server.call(
{Livebook.EPMD.NodePool, parent_node},
{:get_port, :erlang.list_to_binary(name ++ [?@] ++ host)},
5000
)
catch
_, _ -> 0
end
# Default EPMD callbacks
defdelegate start_link(), to: :erl_epmd
defdelegate address_please(name, host, address_family), to: :erl_epmd
defdelegate listen_port_please(name, host), to: :erl_epmd
defdelegate names(host_name), to: :erl_epmd
end