diff --git a/lib/livebook/intellisense.ex b/lib/livebook/intellisense.ex index e25668e9b..d01b489d5 100644 --- a/lib/livebook/intellisense.ex +++ b/lib/livebook/intellisense.ex @@ -23,6 +23,13 @@ defmodule Livebook.Intellisense do map_binding: (Code.binding() -> any()) } + @doc """ + Clear any cache stored related to the given node. + """ + def clear_cache(node) do + IdentifierMatcher.clear_all_loaded(node) + end + @doc """ Resolves an intellisense request as defined by `Runtime`. diff --git a/lib/livebook/intellisense/identifier_matcher.ex b/lib/livebook/intellisense/identifier_matcher.ex index dfce561d1..35cda1b49 100644 --- a/lib/livebook/intellisense/identifier_matcher.ex +++ b/lib/livebook/intellisense/identifier_matcher.ex @@ -99,6 +99,25 @@ defmodule Livebook.Intellisense.IdentifierMatcher do @alias_only_atoms ~w(alias import require)a @alias_only_charlists ~w(alias import require)c + @doc """ + Clears all loaded entries stored for node. + """ + def clear_all_loaded(node) do + :persistent_term.erase({__MODULE__, node}) + end + + defp cached_all_loaded(node) do + case :persistent_term.get({__MODULE__, node}, :error) do + :error -> + modules = Enum.map(:erpc.call(node, :code, :all_loaded, []), &elem(&1, 0)) + :persistent_term.put({__MODULE__, node}, modules) + modules + + [_ | _] = modules -> + modules + end + end + @doc """ Returns a list of identifiers matching the given `hint` together with relevant information. @@ -438,7 +457,7 @@ defmodule Livebook.Intellisense.IdentifierMatcher do end defp container_context_struct_fields(pairs, mod, hint, ctx) do - map = Map.from_struct(mod.__struct__) + map = Map.from_struct(mod.__struct__()) map = filter_out_fields(map, pairs) for {field, default} <- map, @@ -619,7 +638,7 @@ defmodule Livebook.Intellisense.IdentifierMatcher do end defp get_modules(node) do - modules = Enum.map(:erpc.call(node, :code, :all_loaded, []), &elem(&1, 0)) + modules = cached_all_loaded(node) if node == node() and :code.get_mode() == :interactive do modules ++ get_modules_from_applications() diff --git a/lib/livebook/runtime/erl_dist/runtime_server.ex b/lib/livebook/runtime/erl_dist/runtime_server.ex index d55cbb8c3..77d0f7149 100644 --- a/lib/livebook/runtime/erl_dist/runtime_server.ex +++ b/lib/livebook/runtime/erl_dist/runtime_server.ex @@ -307,6 +307,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do @impl true def init(opts) do Process.send_after(self(), :check_owner, @await_owner_timeout) + :net_kernel.monitor_nodes(true, node_type: :all) schedule_memory_usage_report() {:ok, evaluator_supervisor} = ErlDist.EvaluatorSupervisor.start_link() @@ -349,6 +350,11 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do end end + def handle_info({:nodedown, node, _metadata}, state) do + Livebook.Intellisense.clear_cache(node) + {:noreply, state} + end + def handle_info({:DOWN, _, :process, owner, _}, %{owner: owner} = state) do {:stop, :shutdown, state} end diff --git a/test/livebook/intellisense_test.exs b/test/livebook/intellisense_test.exs index d485e8c9e..0975938d5 100644 --- a/test/livebook/intellisense_test.exs +++ b/test/livebook/intellisense_test.exs @@ -230,6 +230,16 @@ defmodule Livebook.IntellisenseTest do ] = Intellisense.get_completion_items("RuntimeE", context, node()) end + test "caches all loaded modules" do + context = eval(do: nil) + Intellisense.get_completion_items("Hub", context, node()) + + key = {Intellisense.IdentifierMatcher, node()} + assert [_ | _] = :persistent_term.get(key, :error) + Intellisense.IdentifierMatcher.clear_all_loaded(node()) + assert :error = :persistent_term.get(key, :error) + end + test "Elixir struct completion lists nested options" do context = eval(do: nil)