Skip to content

Commit

Permalink
fix: ensure some elixir internals are ready (#478)
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 authored May 15, 2024
1 parent 4559d1a commit ff19704
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 ff19704

Please sign in to comment.