-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make remote execution cell work regardless of Elixir and OTP versions (…
…#363) Co-authored-by: José Valim <[email protected]>
- Loading branch information
1 parent
0c2a2cb
commit f107cfa
Showing
6 changed files
with
215 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,6 @@ export function init(ctx, payload) { | |
ctx.importCSS( | ||
"https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap" | ||
); | ||
ctx.importCSS( | ||
"https://cdn.jsdelivr.net/npm/[email protected]/fonts/remixicon.min.css" | ||
); | ||
ctx.importCSS("main.css"); | ||
|
||
const BaseInput = { | ||
|
@@ -214,12 +211,6 @@ export function init(ctx, payload) { | |
inputClass="input input--xs" | ||
:inline | ||
/> | ||
<button type="button" @click="toggleHelpBox" class="icon-button grow"> | ||
<i class="ri ri-questionnaire-line" aria-hidden="true"></i> | ||
</button> | ||
</div> | ||
<div class="help-section" v-if="showHelpBox"> | ||
<p>The Erlang/OTP version of both nodes need to match and the remote node must run Elixir v1.15.7 or later.</p> | ||
</div> | ||
</form> | ||
</div> | ||
|
@@ -228,7 +219,6 @@ export function init(ctx, payload) { | |
data() { | ||
return { | ||
fields: payload.fields, | ||
showHelpBox: false, | ||
}; | ||
}, | ||
|
||
|
@@ -248,9 +238,6 @@ export function init(ctx, payload) { | |
: this.fields["cookie"]; | ||
ctx.setSmartCellEditorIntellisenseNode(node, cookie); | ||
}, | ||
toggleHelpBox() { | ||
this.showHelpBox = !this.showHelpBox; | ||
}, | ||
}, | ||
|
||
mounted() { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
defmodule Kino.RPC do | ||
@moduledoc """ | ||
Functions for working with remote nodes. | ||
""" | ||
|
||
@relative_file Path.relative_to_cwd(__ENV__.file) | ||
|
||
@doc """ | ||
Evaluates the contents given by `string` on the given `node`. | ||
Returns the value returned from evaluation. | ||
The code is analyzed for variable references, they are automatically | ||
extracted from the caller binding and passed to the evaluation. This | ||
means that the evaluated string actually has closure semantics. | ||
The code is parsed and expanded on the remote node. Also, errors | ||
and exists are captured and propagated to the caller. | ||
See `Code.eval_string/3` for available `opts`. | ||
""" | ||
defmacro eval_string(node, string, opts \\ []) do | ||
unless is_binary(string) do | ||
raise ArgumentError, | ||
"Kino.RPC.eval_string/3 expects a string literal as the second argument" | ||
end | ||
|
||
used_var_names = used_var_names(string, __CALLER__) | ||
|
||
binding = for name <- used_var_names, do: {name, Macro.var(name, nil)} | ||
|
||
quote do | ||
Kino.RPC.__remote_eval_string__( | ||
unquote(node), | ||
unquote(string), | ||
unquote(binding), | ||
unquote(opts) | ||
) | ||
end | ||
end | ||
|
||
defp used_var_names(string, env) do | ||
# TODO: only keep :emit_warnings once we require Elixir v1.16+ | ||
case Code.string_to_quoted(string, emit_warnings: false, warn_on_unnecessary_quotes: false) do | ||
{:ok, ast} -> | ||
# This is a simple heuristic, we traverse the unexpanded AST | ||
# and look for any variable node. This means we may have false | ||
# positives if there are macros, but in our use case this is | ||
# acceptable. We may also have false negatives in very specific | ||
# edge cases, such as calling `binding()`, but these are even | ||
# more unlikely. | ||
|
||
names = Map.new(Macro.Env.vars(env)) | ||
|
||
ast | ||
|> Macro.prewalk(MapSet.new(), fn | ||
{name, _, nil} = node, acc when is_map_key(names, name) -> | ||
{node, MapSet.put(acc, name)} | ||
|
||
node, acc -> | ||
{node, acc} | ||
end) | ||
|> elem(1) | ||
|
||
{:error, _} -> | ||
[] | ||
end | ||
end | ||
|
||
@doc false | ||
def __remote_eval_string__(node, string, binding, opts) do | ||
opts = Keyword.validate!(opts, [:file, :line]) | ||
|
||
# We do a nested evaluation to catch errors and capture diagnostics. | ||
# Also, note that `eval_string` returns both result and binding, | ||
# so in order to minimize the data sent between nodes, we bind the | ||
# result and diagnostics to `output` and we rebind `input` to `nil`. | ||
|
||
line = __ENV__.line + 4 | ||
|
||
eval_string = | ||
""" | ||
output = | ||
Code.with_diagnostics([log: false], fn -> | ||
{string, binding, opts} = input | ||
try do | ||
quoted = Code.string_to_quoted!(string, opts) | ||
{value, _binding} = Code.eval_quoted(quoted, binding, opts) | ||
{:ok, value} | ||
catch | ||
kind, error -> | ||
{:error, kind, error, __STACKTRACE__} | ||
end | ||
end) | ||
input = nil | ||
""" | ||
|
||
{nil, binding} = | ||
:erpc.call(node, Code, :eval_string, [ | ||
eval_string, | ||
[input: {string, binding, opts}], | ||
[file: @relative_file, line: line] | ||
]) | ||
|
||
{result, diagnostics} = binding[:output] | ||
|
||
for diagnostic <- diagnostics do | ||
Code.print_diagnostic(diagnostic) | ||
end | ||
|
||
case result do | ||
{:ok, value} -> | ||
value | ||
|
||
{:error, :error, error, stacktrace} -> | ||
error = Exception.normalize(:error, error, stacktrace) | ||
reraise error, stacktrace | ||
|
||
{:error, :throw, value, _stacktrace} -> | ||
throw(value) | ||
|
||
{:error, :exit, reason, _stacktrace} -> | ||
exit(reason) | ||
end | ||
end | ||
end |
Oops, something went wrong.