Packages
livebook
0.19.5
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
# This module allows for initializing connected runtime nodes 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. Both `Runtime.Standalone` and `Runtime.Attached`
# do that and this module contains the shared functionality.
#
# 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`.
@doc """
Livebook modules necessary for evaluation within a runtime node.
"""
@spec required_modules() :: list(module())
def required_modules() do
[
Livebook.Runtime.Definitions,
Livebook.Runtime.Evaluator,
Livebook.Runtime.Evaluator.IOProxy,
Livebook.Runtime.Evaluator.Tracer,
Livebook.Runtime.Evaluator.ObjectTracker,
Livebook.Runtime.Evaluator.ClientTracker,
Livebook.Runtime.Evaluator.Formatter,
Livebook.Runtime.Evaluator.Doctests,
Livebook.Intellisense,
Livebook.Intellisense.Elixir,
Livebook.Intellisense.Elixir.Docs,
Livebook.Intellisense.Elixir.IdentifierMatcher,
Livebook.Intellisense.Elixir.SignatureMatcher,
Livebook.Intellisense.Erlang,
Livebook.Intellisense.Python,
Livebook.Intellisense.Python.Code,
Livebook.Runtime.ErlDist,
Livebook.Runtime.ErlDist.NodeManager,
Livebook.Runtime.ErlDist.RuntimeServer,
Livebook.Runtime.ErlDist.IOForwardGL,
Livebook.Runtime.ErlDist.LoggerGLHandler,
Livebook.Runtime.ErlDist.SmartCellGL,
Livebook.Proxy.Adapter,
Livebook.Proxy.Handler
]
end
@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`.
## Options
* `:node_manager_opts` - see `Livebook.Runtime.ErlDist.NodeManager.start/1`
* `:runtime_server_opts` - see `Livebook.Runtime.ErlDist.RuntimeServer.start_link/1`
"""
@spec initialize(node(), keyword()) :: pid()
def initialize(node, opts \\ []) do
# First, we attempt to communicate with the node manager, in case
# there is one running. Otherwise, the node is not initialized,
# so we need to initialize it and try again
case start_runtime_server(node, opts[:runtime_server_opts] || []) do
{:ok, pid} ->
pid
{:error, :down} ->
unless modules_loaded?(node) do
load_required_modules(node)
end
{:ok, _} = start_node_manager(node, opts[:node_manager_opts] || [])
{:ok, pid} = start_runtime_server(node, opts[:runtime_server_opts] || [])
pid
end
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, opts) do
Livebook.Runtime.ErlDist.NodeManager.start_runtime_server(node, opts)
end
defp modules_loaded?(node) do
:rpc.call(node, Code, :ensure_loaded?, [Livebook.Runtime.ErlDist.NodeManager])
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