Skip to content

Commit 6bae731

Browse files
committed
fix: ignore requests until the relevant engine is started
1 parent 5ac219a commit 6bae731

File tree

8 files changed

+124
-69
lines changed

8 files changed

+124
-69
lines changed

apps/engine/benchmarks/ast_analyze.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Mix.install([:benchee])
2+
13
alias Forge.Ast
24
alias Forge.Document
35

apps/engine/benchmarks/enum_index.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Mix.install([:benchee])
2+
13
alias Engine.Search.Indexer
24

35
path =

apps/engine/benchmarks/ets_bench.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Mix.install([:benchee])
2+
13
alias Forge.Project
24

35
alias Engine.Search.Store.Backends.Ets

apps/engine/benchmarks/versions_bench.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
Mix.install([:benchee])
2+
13
alias Forge.VM.Versions
24

35
Benchee.run(%{

apps/engine/mix.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ defmodule Engine.MixProject do
4444

4545
defp deps do
4646
[
47-
{:benchee, "~> 1.3", only: :test},
4847
{:deps_nix, "~> 2.4", only: :dev},
4948
Mix.Credo.dependency(),
5049
Mix.Dialyzer.dependency(),

apps/expert/lib/expert.ex

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
defmodule Expert do
2+
alias Forge.Project
3+
alias Expert.ActiveProjects
24
alias Expert.Protocol.Convert
35
alias Expert.Protocol.Id
46
alias Expert.Provider.Handlers
@@ -94,42 +96,72 @@ defmodule Expert do
9496
def handle_request(request, lsp) do
9597
state = assigns(lsp).state
9698

97-
if state.engine_initialized? do
98-
with {:ok, handler} <- fetch_handler(request),
99-
{:ok, request} <- Convert.to_native(request),
100-
{:ok, response} <- handler.handle(request, state.configuration),
101-
{:ok, response} <- Expert.Protocol.Convert.to_lsp(response) do
102-
{:reply, response, lsp}
103-
else
104-
{:error, {:unhandled, _}} ->
105-
Logger.info("Unhandled request: #{request.method}")
106-
107-
{:reply,
108-
%GenLSP.ErrorResponse{
109-
code: GenLSP.Enumerations.ErrorCodes.method_not_found(),
110-
message: "Method not found"
111-
}, lsp}
112-
113-
error ->
114-
message = "Failed to handle #{request.method}, #{inspect(error)}"
115-
Logger.error(message)
116-
117-
{:reply,
118-
%GenLSP.ErrorResponse{
119-
code: GenLSP.Enumerations.ErrorCodes.internal_error(),
120-
message: message
121-
}, lsp}
122-
end
99+
with {:ok, handler} <- fetch_handler(request),
100+
{:ok, request} <- Convert.to_native(request),
101+
:ok <- check_engine_initialized(request),
102+
{:ok, response} <- handler.handle(request, state.configuration),
103+
{:ok, response} <- Expert.Protocol.Convert.to_lsp(response) do
104+
{:reply, response, lsp}
123105
else
124-
GenLSP.warning(
125-
lsp,
126-
"Received request #{request.method} before engine was initialized. Ignoring."
127-
)
106+
{:error, {:unhandled, _}} ->
107+
Logger.info("Unhandled request: #{request.method}")
128108

129-
{:noreply, lsp}
109+
{:reply,
110+
%GenLSP.ErrorResponse{
111+
code: GenLSP.Enumerations.ErrorCodes.method_not_found(),
112+
message: "Method not found"
113+
}, lsp}
114+
115+
{:error, :engine_not_initialized, project} ->
116+
GenLSP.info(
117+
lsp,
118+
"Received request #{request.method} before engine for #{project && Project.name(project)} was initialized. Ignoring."
119+
)
120+
121+
{:reply, nil, lsp}
122+
123+
error ->
124+
message = "Failed to handle #{request.method}, #{inspect(error)}"
125+
Logger.error(message)
126+
127+
{:reply,
128+
%GenLSP.ErrorResponse{
129+
code: GenLSP.Enumerations.ErrorCodes.internal_error(),
130+
message: message
131+
}, lsp}
130132
end
131133
end
132134

135+
defp check_engine_initialized(request) do
136+
if document_request?(request) do
137+
case Forge.Document.Container.context_document(request, nil) do
138+
%Forge.Document{} = document ->
139+
projects = ActiveProjects.projects()
140+
project = Project.project_for_document(projects, document)
141+
142+
if ActiveProjects.active?(project) do
143+
:ok
144+
else
145+
{:error, :engine_not_initialized, project}
146+
end
147+
148+
nil ->
149+
{:error, :engine_not_initialized, nil}
150+
end
151+
else
152+
:ok
153+
end
154+
end
155+
156+
defp document_request?(%{document: %Forge.Document{}}), do: true
157+
158+
defp document_request?(%{params: params}) do
159+
document_request?(params)
160+
end
161+
162+
defp document_request?(%{text_document: %{uri: _}}), do: true
163+
defp document_request?(_), do: false
164+
133165
def handle_notification(%GenLSP.Notifications.Initialized{}, lsp) do
134166
Logger.info("Server initialized, registering capabilities")
135167
registrations = registrations()
@@ -175,18 +207,6 @@ defmodule Expert do
175207
end
176208
end
177209

178-
def handle_info(:engine_initialized, lsp) do
179-
state = assigns(lsp).state
180-
181-
new_state = %{state | engine_initialized?: true}
182-
183-
lsp = assign(lsp, state: new_state)
184-
185-
Logger.info("Engine initialized")
186-
187-
{:noreply, lsp}
188-
end
189-
190210
def handle_info(:default_config, lsp) do
191211
state = assigns(lsp).state
192212

apps/expert/lib/expert/active_projects.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Expert.ActiveProjects do
55
Since GenLSP events happen asynchronously, we use an ets table to keep track of
66
them and avoid race conditions when we try to update the list of active projects.
77
"""
8+
alias Forge.Project
89

910
use GenServer
1011

@@ -21,6 +22,10 @@ defmodule Expert.ActiveProjects do
2122

2223
def init(_) do
2324
__MODULE__ = :ets.new(__MODULE__, [:set, :named_table, :public, read_concurrency: true])
25+
26+
__MODULE__.Ready =
27+
:ets.new(__MODULE__.Ready, [:set, :named_table, :public, read_concurrency: true])
28+
2429
{:ok, nil}
2530
end
2631

@@ -47,4 +52,19 @@ defmodule Expert.ActiveProjects do
4752
:ets.delete_all_objects(__MODULE__)
4853
add_projects(new_projects)
4954
end
55+
56+
def set_ready(%Project{} = project, ready?) when is_boolean(ready?) do
57+
if ready? do
58+
:ets.insert(__MODULE__.Ready, {project.root_uri, true})
59+
else
60+
:ets.delete(__MODULE__.Ready, project.root_uri)
61+
end
62+
end
63+
64+
def active?(%Project{} = project) do
65+
case :ets.lookup(__MODULE__.Ready, project.root_uri) do
66+
[{_, true}] -> true
67+
_ -> false
68+
end
69+
end
5070
end

apps/expert/lib/expert/state.ex

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ defmodule Expert.State do
1818

1919
defstruct configuration: nil,
2020
initialized?: false,
21-
engine_initialized?: false,
2221
shutdown_received?: false,
2322
in_flight_requests: %{}
2423

@@ -71,13 +70,11 @@ defmodule Expert.State do
7170

7271
ActiveProjects.set_projects(projects)
7372

74-
Task.Supervisor.start_child(:expert_task_queue, fn ->
75-
for project <- projects do
73+
for project <- projects do
74+
Task.Supervisor.start_child(:expert_task_queue, fn ->
7675
ensure_project_node_started(project)
77-
end
78-
79-
send(Expert, :engine_initialized)
80-
end)
76+
end)
77+
end
8178

8279
{:ok, response, new_state}
8380
end
@@ -152,22 +149,31 @@ defmodule Expert.State do
152149
projects = ActiveProjects.projects()
153150
project = Project.project_for_uri(projects, uri)
154151

155-
case Document.Store.get_and_update(
156-
uri,
157-
# TODO: this function needs to accept the GenLSP data structure
158-
&Document.apply_content_changes(&1, version, params.content_changes)
159-
) do
160-
{:ok, updated_source} ->
161-
updated_message =
162-
file_changed(
163-
uri: updated_source.uri,
164-
open?: true,
165-
from_version: version,
166-
to_version: updated_source.version
167-
)
168-
169-
EngineApi.broadcast(project, updated_message)
170-
EngineApi.compile_document(project, updated_source)
152+
with true <- ActiveProjects.active?(project),
153+
{:ok, updated_source} <-
154+
Document.Store.get_and_update(
155+
uri,
156+
# TODO: this function needs to accept the GenLSP data structure
157+
&Document.apply_content_changes(&1, version, params.content_changes)
158+
) do
159+
updated_message =
160+
file_changed(
161+
uri: updated_source.uri,
162+
open?: true,
163+
from_version: version,
164+
to_version: updated_source.version
165+
)
166+
167+
EngineApi.broadcast(project, updated_message)
168+
EngineApi.compile_document(project, updated_source)
169+
{:ok, state}
170+
else
171+
false ->
172+
GenLSP.info(
173+
Expert.get_lsp(),
174+
"Received request textDocument/didChange before engine for #{Project.name(project)} was initialized. Ignoring."
175+
)
176+
171177
{:ok, state}
172178

173179
error ->
@@ -269,6 +275,7 @@ defmodule Expert.State do
269275
defp ensure_project_node_started(project) do
270276
case Expert.Project.Supervisor.start(project) do
271277
{:ok, _pid} ->
278+
ActiveProjects.set_ready(project, true)
272279
Logger.info("Project node started for #{Project.name(project)}")
273280

274281
GenLSP.log(Expert.get_lsp(), "Started project node for #{Project.name(project)}")
@@ -281,9 +288,9 @@ defmodule Expert.State do
281288
"Failed to start project node for #{Project.name(project)}: #{inspect(reason, pretty: true)}"
282289
)
283290

284-
GenLSP.log(
291+
GenLSP.error(
285292
Expert.get_lsp(),
286-
"Failed to start project node for #{Project.name(project)}"
293+
"Failed to start project node for #{Project.name(project)}: #{inspect(reason, pretty: true)}"
287294
)
288295

289296
{:error, reason}
@@ -292,6 +299,7 @@ defmodule Expert.State do
292299

293300
defp stop_project_node(project) do
294301
Expert.Project.Supervisor.stop(project)
302+
ActiveProjects.set_ready(project, false)
295303

296304
GenLSP.log(
297305
Expert.get_lsp(),

0 commit comments

Comments
 (0)