Skip to content

Commit

Permalink
avoid blocking suggest contract calls from main server loop
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsamson committed Jan 8, 2024
1 parent 13252d9 commit 5f20d5e
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 18 deletions.
8 changes: 4 additions & 4 deletions apps/language_server/lib/language_server/dialyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
)
end

def suggest_contracts(server \\ {:global, {self(), __MODULE__}}, files)
def suggest_contracts(parent \\ self(), files)

def suggest_contracts(_server, []), do: []
def suggest_contracts(_parent, []), do: []

def suggest_contracts(server, files) do
def suggest_contracts(parent, files) do
try do
GenServer.call(server, {:suggest_contracts, files}, :infinity)
GenServer.call({:global, {parent, __MODULE__}}, {:suggest_contracts, files}, :infinity)
catch
kind, payload ->
{payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__)
Expand Down
38 changes: 24 additions & 14 deletions apps/language_server/lib/language_server/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ defmodule ElixirLS.LanguageServer.Server do
case state do
%{analysis_ready?: true, source_files: %{^uri => %{dirty?: false}}} ->
abs_path = SourceFile.Path.absolute_from_uri(uri, state.project_dir)
{:reply, Dialyzer.suggest_contracts([abs_path]), state}
parent = self()
spawn(fn ->
contracts = Dialyzer.suggest_contracts(parent, [abs_path])
GenServer.reply(from, contracts)
end)
{:noreply, state}

%{source_files: %{^uri => _}} ->
# file not saved or analysis not finished
Expand Down Expand Up @@ -1521,19 +1526,24 @@ defmodule ElixirLS.LanguageServer.Server do
Map.fetch!(state.source_files, uri).dirty?
end)

contracts_by_file =
not_dirty
|> Enum.map(fn {_from, uri} -> SourceFile.Path.from_uri(uri) end)
|> Dialyzer.suggest_contracts()
|> Enum.group_by(fn {file, _, _, _, _} -> file end)

for {from, uri} <- not_dirty do
contracts =
contracts_by_file
|> Map.get(SourceFile.Path.from_uri(uri), [])

GenServer.reply(from, contracts)
end
# Dialyzer.suggest_contracts is blocking and can take a long time to complete
# if we block here we hang the server
parent = self()
spawn(fn ->
contracts_by_file =
not_dirty
|> Enum.map(fn {_from, uri} -> SourceFile.Path.from_uri(uri) end)
|> then(fn uris -> Dialyzer.suggest_contracts(parent, uris) end)
|> Enum.group_by(fn {file, _, _, _, _} -> file end)

for {from, uri} <- not_dirty do
contracts =
contracts_by_file
|> Map.get(SourceFile.Path.from_uri(uri), [])

GenServer.reply(from, contracts)
end
end)

%{state | analysis_ready?: true, awaiting_contracts: dirty}
else
Expand Down

0 comments on commit 5f20d5e

Please sign in to comment.