Current section

Files

Jump to
livebook lib livebook runtime remote_utils.ex
Raw

lib/livebook/runtime/remote_utils.ex

defmodule Livebook.Runtime.RemoteUtils do
# Shared code for runtimes using a remote node.
require Logger
@doc """
The port that the remote runtime node uses for distribution.
"""
@spec remote_port() :: pos_integer()
def remote_port(), do: 44444
@doc """
Encodes information for the remote node.
The returned value should be passed when starting the remote node
via the LIVEBOOK_RUNTIME environment variable.
"""
@spec encode_runtime_data(String.t()) :: String.t()
def encode_runtime_data(node_base) do
%{
node_base: node_base,
cookie: Node.get_cookie(),
dist_port: remote_port()
}
|> :erlang.term_to_binary()
|> Base.encode64()
end
@doc """
Discovers a free TCP port.
"""
@spec get_free_port!() :: pos_integer()
def get_free_port!() do
{:ok, socket} = :gen_tcp.listen(0, active: false, reuseaddr: true)
{:ok, port} = :inet.port(socket)
:gen_tcp.close(socket)
port
end
@doc """
Fetches information from the remote runtime node.
"""
@spec fetch_runtime_info(node()) :: %{pid: pid()}
def fetch_runtime_info(child_node) do
# Note: it is Livebook that starts the runtime node, so we know
# that the node runs Livebook release of the exact same version
#
# Also, the remote node already has all the runtime modules in
# the code path, compiled for its Elixir version, so we don't
# need to check for matching Elixir version.
:erpc.call(child_node, :persistent_term, :get, [:livebook_runtime_info])
end
@doc """
Attempts connecting to the given node.
Makes several connect attempts over a few seconds.
"""
@spec connect(node()) :: :ok | {:error, String.t()}
def connect(node) do
connect_loop(node, 40, 250)
end
defp connect_loop(_node, 0, _interval) do
{:error, "could not establish connection with the node"}
end
defp connect_loop(node, attempts, interval) do
if Node.connect(node) do
:ok
else
Process.sleep(interval)
connect_loop(node, attempts - 1, interval)
end
end
@doc """
Starts a runtime server on the remote node.
"""
@spec initialize_node(node()) :: pid()
def initialize_node(child_node) do
init_opts = [
runtime_server_opts: [
extra_smart_cell_definitions: Livebook.Runtime.Definitions.smart_cell_definitions()
]
]
Livebook.Runtime.ErlDist.initialize(child_node, init_opts)
end
@doc """
Wraps a potentially long operation.
Logs operation duration after completion. On failure, also logs the
error.
"""
@spec with_log(String.t(), (-> term())) :: term()
def with_log(name, fun) do
{microseconds, result} = :timer.tc(fun)
milliseconds = div(microseconds, 1000)
case result do
{:error, error} ->
Logger.debug("#{name} FAILED in #{milliseconds}ms, error: #{error}")
_ ->
Logger.debug("#{name} finished in #{milliseconds}ms")
end
result
end
end