Packages
livebook
0.4.0
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/erl_dist.ex
defmodule Livebook.Runtime.ErlDist do
@moduledoc false
# This module allows for initializing nodes connected using
# Erlang Distribution with modules and processes necessary for evaluation.
#
# To ensure proper isolation between sessions,
# code evaluation may take place in a separate Elixir runtime,
# which also makes it easy to terminate the whole
# evaluation environment without stopping Livebook.
# This is what `Runtime.ElixirStandalone`, `Runtime.MixStandalone`
# and `Runtime.Attached` do, so this module contains the shared
# functionality they need.
#
# To work with a separate node, we have to inject the necessary
# Livebook modules there and also start the relevant processes
# related to evaluation. Fortunately Erlang allows us to send modules
# binary representation to the other node and load them dynamically.
#
# For further details see `Livebook.Runtime.ErlDist.NodeManager`.
# Modules to load into the connected node.
@required_modules [
Livebook.Evaluator,
Livebook.Evaluator.IOProxy,
Livebook.Evaluator.ObjectTracker,
Livebook.Evaluator.DefaultFormatter,
Livebook.Intellisense,
Livebook.Intellisense.Docs,
Livebook.Intellisense.IdentifierMatcher,
Livebook.Intellisense.SignatureMatcher,
Livebook.Runtime.ErlDist,
Livebook.Runtime.ErlDist.NodeManager,
Livebook.Runtime.ErlDist.RuntimeServer,
Livebook.Runtime.ErlDist.EvaluatorSupervisor,
Livebook.Runtime.ErlDist.IOForwardGL,
Livebook.Runtime.ErlDist.LoggerGLBackend
]
@doc """
Starts a runtime server on the given node.
If necessary, the required modules are loaded
into the given node and the node manager process
is started with `node_manager_opts`.
"""
@spec initialize(node(), keyword()) :: pid()
def initialize(node, node_manager_opts \\ []) do
unless modules_loaded?(node) do
load_required_modules(node)
end
unless node_manager_started?(node) do
start_node_manager(node, node_manager_opts)
end
start_runtime_server(node)
end
defp load_required_modules(node) do
for module <- @required_modules do
{_module, binary, filename} = :code.get_object_code(module)
case :rpc.call(node, :code, :load_binary, [module, filename, binary]) do
{:module, _} ->
:ok
{:error, reason} ->
local_otp = :erlang.system_info(:otp_release)
remote_otp = :rpc.call(node, :erlang, :system_info, [:otp_release])
if local_otp != remote_otp do
raise RuntimeError,
"failed to load #{inspect(module)} module into the remote node, potentially due to Erlang/OTP version mismatch, reason: #{inspect(reason)} (local #{local_otp} != remote #{remote_otp})"
else
raise RuntimeError,
"failed to load #{inspect(module)} module into the remote node, reason: #{inspect(reason)}"
end
end
end
end
defp start_node_manager(node, opts) do
:rpc.call(node, Livebook.Runtime.ErlDist.NodeManager, :start, [opts])
end
defp start_runtime_server(node) do
Livebook.Runtime.ErlDist.NodeManager.start_runtime_server(node)
end
defp modules_loaded?(node) do
:rpc.call(node, Code, :ensure_loaded?, [Livebook.Runtime.ErlDist.NodeManager])
end
defp node_manager_started?(node) do
case :rpc.call(node, Process, :whereis, [Livebook.Runtime.ErlDist.NodeManager]) do
nil -> false
_pid -> true
end
end
@doc """
Unloads the previously loaded Livebook modules from the caller node.
"""
def unload_required_modules() do
for module <- @required_modules do
# If we attached, detached and attached again, there may still
# be deleted module code, so purge it first.
:code.purge(module)
:code.delete(module)
end
end
end