Skip to content

Commit

Permalink
fix: ensure some elixir internals are ready
Browse files Browse the repository at this point in the history
Frequently we'd see the following error in tests, and some users were
seeing in production:

```
[error] Bad RPC call to node nextls-runtime-1715431446385794000@MacBook-XXX: {:EXIT, {:badarg, [{:ets, :lookup, [:elixir_config, :parser_options], [error_info: %{cause: :id, module: :erl_stdlib_errors}]}, {:elixir_config, :get, 1, [file: ~c"src/elixir_config.erl", line: 21]}, {:elixir_compiler, :string, 3, [file: ~c"src/elixir_compiler.erl", line: 7]}, {Module.ParallelChecker, :verify, 1, [file: ~c"lib/module/parallel_checker.ex", line: 112]}, {Code, :compile_file, 1, []}]}}
```

I believe that this is caused by an internal ETS table not being booted
yet, so we not await on it being present on the project node.

Closes #467
  • Loading branch information
mhanberg committed May 15, 2024
1 parent 4559d1a commit 10a3537
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 21 deletions.
56 changes: 40 additions & 16 deletions lib/next_ls/runtime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -231,22 +231,32 @@ defmodule NextLS.Runtime do
true <- connect(node, port, 120) do
NextLS.Logger.info(logger, "Connected to node #{node}")

:next_ls
|> :code.priv_dir()
|> Path.join("monkey/_next_ls_private_compiler.ex")
|> then(&:rpc.call(node, Code, :compile_file, [&1]))
|> tap(fn
{:badrpc, error} ->
NextLS.Logger.error(logger, "Bad RPC call to node #{node}: #{inspect(error)}")
send(me, {:cancel, error})

_ ->
:ok
end)

{:ok, _} = :rpc.call(node, :_next_ls_private_compiler, :start, [])

send(me, {:node, node})
result =
:next_ls
|> :code.priv_dir()
|> Path.join("monkey/_next_ls_private_compiler.ex")
|> then(fn path ->
if await_config_table(node, 5) do
:rpc.call(node, Code, :compile_file, [path])
else
{:badrpc, "internal ets table not found"}
end
end)
|> then(fn
{:badrpc, error} ->
NextLS.Logger.error(logger, "Bad RPC call to node #{node}: #{inspect(error)}")
send(me, {:cancel, error})
:error

_ ->
:ok
end)

if result == :ok do
{:ok, _} = :rpc.call(node, :_next_ls_private_compiler, :start, [])

send(me, {:node, node})
end
else
error ->
send(me, {:cancel, error})
Expand Down Expand Up @@ -275,6 +285,20 @@ defmodule NextLS.Runtime do
end
end

defp await_config_table(_node, 0) do
false
end

defp await_config_table(node, attempts) do
# this is an Elixir implementation detail, handle with care
if :undefined == :rpc.call(node, :ets, :whereis, [:elixir_config]) do
Process.sleep(100)
await_config_table(node, attempts - 1)
else
true
end
end

@impl GenServer
def handle_call(:ready?, _from, state) when is_ready(state) do
{:reply, true, state}
Expand Down
8 changes: 3 additions & 5 deletions priv/monkey/_next_ls_private_compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1032,14 +1032,13 @@ defmodule :_next_ls_private_compiler do
@moduledoc false

def start do
Code.put_compiler_option(:parser_options, columns: true, token_metadata: true)

children = [
:_next_ls_private_compiler_worker
]

# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: :_next_ls_private_application_supervisor]
{:ok, pid} = Supervisor.start_link(children, opts)
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one, name: :_next_ls_private_application_supervisor)
Process.unlink(pid)
{:ok, pid}
end
Expand All @@ -1049,7 +1048,6 @@ defmodule :_next_ls_private_compiler do
def compile do
# keep stdout on this node
Process.group_leader(self(), Process.whereis(:user))
Code.put_compiler_option(:parser_options, columns: true, token_metadata: true)

Code.put_compiler_option(:tracers, [NextLSPrivate.DepTracer | @tracers])

Expand Down

0 comments on commit 10a3537

Please sign in to comment.