Skip to content

Commit

Permalink
fix: single thread compiler requests (#401)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg authored Mar 27, 2024
1 parent c9e260b commit e6aff2b
Show file tree
Hide file tree
Showing 9 changed files with 1,097 additions and 176 deletions.
294 changes: 194 additions & 100 deletions lib/next_ls.ex

Large diffs are not rendered by default.

38 changes: 20 additions & 18 deletions lib/next_ls/progress.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@ defmodule NextLS.Progress do
@moduledoc false
@env Mix.env()
def start(lsp, token, msg) do
# FIXME: gen_lsp should allow stubbing requests so we don't have to
# set this in every test. For now, don't send it in the test env
if @env != :test do
GenLSP.request(lsp, %GenLSP.Requests.WindowWorkDoneProgressCreate{
id: System.unique_integer([:positive]),
params: %GenLSP.Structures.WorkDoneProgressCreateParams{
token: token
}
})
end
Task.start(fn ->
# FIXME: gen_lsp should allow stubbing requests so we don't have to
# set this in every test. For now, don't send it in the test env
if @env != :test do
GenLSP.request(lsp, %GenLSP.Requests.WindowWorkDoneProgressCreate{
id: System.unique_integer([:positive]),
params: %GenLSP.Structures.WorkDoneProgressCreateParams{
token: token
}
})
end

GenLSP.notify(lsp, %GenLSP.Notifications.DollarProgress{
params: %GenLSP.Structures.ProgressParams{
token: token,
value: %GenLSP.Structures.WorkDoneProgressBegin{
kind: "begin",
title: msg
GenLSP.notify(lsp, %GenLSP.Notifications.DollarProgress{
params: %GenLSP.Structures.ProgressParams{
token: token,
value: %GenLSP.Structures.WorkDoneProgressBegin{
kind: "begin",
title: msg
}
}
}
})
})
end)
end

def stop(lsp, token, msg \\ nil) do
Expand Down
64 changes: 23 additions & 41 deletions lib/next_ls/runtime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ defmodule NextLS.Runtime do
sname = "nextls-runtime-#{System.system_time()}"
name = Keyword.fetch!(opts, :name)
working_dir = Keyword.fetch!(opts, :working_dir)
lsp_pid = Keyword.fetch!(opts, :lsp_pid)
uri = Keyword.fetch!(opts, :uri)
parent = Keyword.fetch!(opts, :parent)
logger = Keyword.fetch!(opts, :logger)
Expand Down Expand Up @@ -222,6 +223,8 @@ defmodule NextLS.Runtime do
:ok
end)

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

send(me, {:node, node})
else
error ->
Expand All @@ -237,6 +240,7 @@ defmodule NextLS.Runtime do
port: port,
task_supervisor: task_supervisor,
logger: logger,
lsp_pid: lsp_pid,
parent: parent,
errors: nil,
registry: registry,
Expand Down Expand Up @@ -279,54 +283,32 @@ defmodule NextLS.Runtime do
end
end

def handle_call({:compile, opts}, from, %{node: node} = state) do
for {_ref, {task_pid, _from}} <- state.compiler_refs, do: Process.exit(task_pid, :kill)

task =
Task.Supervisor.async_nolink(state.task_supervisor, fn ->
if opts[:force] do
File.rm_rf!(Path.join(state.working_dir, ".elixir-tools/_build"))
end

case :rpc.call(node, :_next_ls_private_compiler, :compile, []) do
{:badrpc, error} ->
NextLS.Logger.error(state.logger, "Bad RPC call to node #{node}: #{inspect(error)}")
[]

{_, diagnostics} when is_list(diagnostics) ->
Registry.dispatch(state.registry, :extensions, fn entries ->
for {pid, _} <- entries, do: send(pid, {:compiler, diagnostics})
end)

NextLS.Logger.info(state.logger, "Compiled #{state.name}!")

diagnostics

{:error, %Mix.Error{message: "Can't continue due to errors on dependencies"}} ->
{:runtime_failed, state.name, {:error, :deps}}
def handle_call({:compile, opts}, _from, %{node: node} = state) do
opts =
opts
|> Keyword.put_new(:working_dir, state.working_dir)
|> Keyword.put_new(:registry, state.registry)
|> Keyword.put(:from, self())

unknown ->
NextLS.Logger.warning(state.logger, "Unexpected compiler response: #{inspect(unknown)}")
[]
end
end)
with {:badrpc, error} <- :rpc.call(node, :_next_ls_private_compiler_worker, :enqueue_compiler, [opts]) do
NextLS.Logger.error(state.logger, "Bad RPC call to node #{node}: #{inspect(error)}")
end

{:noreply, %{state | compiler_refs: Map.put(state.compiler_refs, task.ref, {task.pid, from})}}
{:reply, :ok, state}
end

@impl GenServer
def handle_info({ref, errors}, %{compiler_refs: compiler_refs} = state) when is_map_key(compiler_refs, ref) do
Process.demonitor(ref, [:flush])

orig = elem(compiler_refs[ref], 1)
GenServer.reply(orig, errors)

{:noreply, %{state | compiler_refs: Map.delete(compiler_refs, ref)}}
# NOTE: these two callbacks are basically to forward the messages from the runtime to the LSP
# LSP process so that progress messages can be dispatched
def handle_info({:compiler_result, caller_ref, result}, state) do
# we add the runtime name into the message
send(state.lsp_pid, {:compiler_result, caller_ref, state.name, result})
{:noreply, state}
end

def handle_info({:DOWN, ref, :process, _pid, _reason}, %{compiler_refs: compiler_refs} = state)
when is_map_key(compiler_refs, ref) do
{:noreply, %{state | compiler_refs: Map.delete(compiler_refs, ref)}}
def handle_info({:compiler_canceled, _caller_ref} = msg, state) do
send(state.lsp_pid, msg)
{:noreply, state}
end

def handle_info({:DOWN, _, :port, port, _}, %{port: port} = state) do
Expand Down
4 changes: 3 additions & 1 deletion lib/next_ls/runtime/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule NextLS.Runtime.Supervisor do
def init(init_arg) do
name = init_arg[:name]
lsp = init_arg[:lsp]
lsp_pid = init_arg[:lsp_pid]
registry = init_arg[:registry]
logger = init_arg[:logger]
hidden_folder = init_arg[:path]
Expand All @@ -34,7 +35,8 @@ defmodule NextLS.Runtime.Supervisor do
name: db_name,
runtime: name,
activity: db_activity},
{NextLS.Runtime, init_arg[:runtime] ++ [name: name, registry: registry, parent: sidecar_name, db: db_name]}
{NextLS.Runtime,
init_arg[:runtime] ++ [name: name, registry: registry, parent: sidecar_name, lsp_pid: lsp_pid, db: db_name]}
]

Supervisor.init(children, strategy: :one_for_one)
Expand Down
Loading

0 comments on commit e6aff2b

Please sign in to comment.