From f4685d01266b4afb7f557d9a361fc7770aa22ec6 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Wed, 15 May 2024 10:06:15 -0400 Subject: [PATCH] fix: ensure some elixir internals are ready (#478) 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 --- .github/workflows/release.yaml | 24 +++++----- lib/next_ls/runtime.ex | 56 +++++++++++++++++------- priv/monkey/_next_ls_private_compiler.ex | 8 ++-- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 49102ae6..3aa8f98e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,22 +9,22 @@ permissions: pull-requests: write jobs: - commitlint: - runs-on: ubuntu-latest - name: commitlint + # commitlint: + # runs-on: ubuntu-latest + # name: commitlint - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Install Deps - run: yarn install - - name: Lint PR Title - run: yarn commitlint --from "${{ github.event.before }}" + # steps: + # - uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + # - name: Install Deps + # run: yarn install + # - name: Lint PR Title + # run: yarn commitlint --from "${{ github.event.before }}" release: name: release - needs: commitlint + # needs: commitlint runs-on: ubuntu-latest outputs: release_created: ${{ steps.release.outputs.release_created }} diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index 4ca68420..749122d6 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -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}) @@ -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} diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index 3e34a6b1..d673b3ea 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -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 @@ -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])