From 37c05dd20444f10d3cc0fcf5622fdc2b56f4e8d4 Mon Sep 17 00:00:00 2001 From: Dallin Osmun Date: Fri, 5 Sep 2025 10:11:27 -0600 Subject: [PATCH 1/2] ensure id always exists while handling requests --- lib/gen_lsp.ex | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/gen_lsp.ex b/lib/gen_lsp.ex index fa2e0b4..094c73c 100644 --- a/lib/gen_lsp.ex +++ b/lib/gen_lsp.ex @@ -332,7 +332,7 @@ defmodule GenLSP do start = System.system_time(:microsecond) :telemetry.execute([:gen_lsp, :request, :client, :start], %{}) - me = self() + id = request["id"] {:ok, task} = attempt( @@ -352,7 +352,7 @@ defmodule GenLSP do packet = %{ "jsonrpc" => "2.0", - "id" => Process.get(:request_id), + "id" => id, "error" => output } @@ -363,10 +363,7 @@ defmodule GenLSP do _ -> case GenLSP.Requests.new(request) do - {:ok, %{id: id} = req} -> - Process.put(:request_id, id) - send(me, {:request_id, id}) - + {:ok, req} -> result = :telemetry.span([:gen_lsp, :handle_request], %{method: req.method}, fn -> {lsp.mod.handle_request(req, lsp), %{}} @@ -455,7 +452,7 @@ defmodule GenLSP do packet = %{ "jsonrpc" => "2.0", - "id" => request["id"], + "id" => id, "error" => output } @@ -464,12 +461,6 @@ defmodule GenLSP do end ) - id = - receive do - {:request_id, id} -> - id - end - tasks = Map.put(lsp.tasks, id, task) lsp = put_in(lsp.tasks, tasks) From 5710861202e0b4f886106a8f7a9aabf45547437d Mon Sep 17 00:00:00 2001 From: Dallin Osmun Date: Fri, 5 Sep 2025 20:15:09 -0600 Subject: [PATCH 2/2] add test to ensure server still responds after handling incomplete requests --- test/gen_lsp_test.exs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/gen_lsp_test.exs b/test/gen_lsp_test.exs index 63f7e8f..0af3107 100644 --- a/test/gen_lsp_test.exs +++ b/test/gen_lsp_test.exs @@ -324,7 +324,7 @@ defmodule GenLSPTest do assert log =~ expected_msg end - test "returns an invalid request when the paylaod is not parseable, but is still deemed a request", + test "returns an invalid request when the payload is not parseable, but is still deemed a request", %{client: client} do assert :ok == request(client, %{"id" => "bingo", "random" => "stuff"}) @@ -336,6 +336,38 @@ defmodule GenLSPTest do 500 end + test "gracefully recovers from invalid requests and continues to accept requests", + %{client: client} do + assert :ok == request(client, %{"id" => "whoops", "random" => "again"}) + + assert_error "whoops", + %{ + "message" => "Invalid request from the client" <> _, + "code" => -32600 + }, + 500 + + id = System.unique_integer([:positive]) + + assert :ok == + request(client, %{ + "jsonrpc" => "2.0", + "method" => "initialize", + "params" => %{"capabilities" => %{}}, + "id" => id + }) + + assert_result ^id, + %{ + "capabilities" => %{ + "callHierarchyProvider" => %{"workDoneProgress" => true}, + "experimental" => nil + }, + "serverInfo" => %{"name" => "Test LSP"} + }, + 500 + end + test "logs when an invalid payload is received", %{client: client} do assert capture_log(fn -> assert :ok == notify(client, %{"random" => "stuff"})