Skip to content

Commit f38329f

Browse files
authored
Restore request cancellation with correct implementation (#3791)
Ruby LSP doesn't currently cancel requests, because it processes `$/cancelRequest` notifications in the same queue as other requests, so previous requests will get processed before they get the chance to get cancelled. This was initially addressed in #2939, with cancelled requests returning an error response. This is matching the LSP spec, which requires either an incomplete or an error response. However, this started causing issues with Neovim as reported in #3019, so that implementation was ultimately reverted. The problem wasn't actually the error responses, it was that Ruby LSP was also returning a regular response, *on top of* the error response. So, a cancellation notification was resulting in *two* responses being returned for the same request, which rightfully caused errors in Neovim, as it didn't know how to handle the 2nd response. This happened because the `next` keyword breaks out of the nearest loop *or* closure, which was the `Mutex#synchronize` block, it didn't actually skip to the next iteration. This caused `process_message` to execute even if the cancel response was already sent, which ended up sending another (regular) response. To avoid introducing boolean local variables, I extracted the loop body into a separate method, so that I can just use `return` to break out. I verified that this works as expected in Zed, so I also expect it to work well in Neovim.
1 parent aae4827 commit f38329f

File tree

2 files changed

+25
-15
lines changed

2 files changed

+25
-15
lines changed

lib/ruby_lsp/base_server.rb

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def start
8383
# The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
8484
# else is pushed into the incoming queue
8585
case method
86-
when "initialize", "initialized", "rubyLsp/diagnoseState"
86+
when "initialize", "initialized", "rubyLsp/diagnoseState", "$/cancelRequest"
8787
process_message(message)
8888
when "shutdown"
8989
@global_state.synchronize do
@@ -154,20 +154,29 @@ def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
154154
def new_worker
155155
Thread.new do
156156
while (message = @incoming_queue.pop)
157-
id = message[:id]
158-
159-
# Check if the request was cancelled before trying to process it
160-
@global_state.synchronize do
161-
if id && @cancelled_requests.include?(id)
162-
send_message(Result.new(id: id, response: nil))
163-
@cancelled_requests.delete(id)
164-
next
165-
end
166-
end
157+
handle_incoming_message(message)
158+
end
159+
end
160+
end
167161

168-
process_message(message)
162+
#: (Hash[Symbol, untyped]) -> void
163+
def handle_incoming_message(message)
164+
id = message[:id]
165+
166+
# Check if the request was cancelled before trying to process it
167+
@global_state.synchronize do
168+
if id && @cancelled_requests.include?(id)
169+
send_message(Error.new(
170+
id: id,
171+
code: Constant::ErrorCodes::REQUEST_CANCELLED,
172+
message: "Request #{id} was cancelled",
173+
))
174+
@cancelled_requests.delete(id)
175+
return
169176
end
170177
end
178+
179+
process_message(message)
171180
end
172181

173182
#: ((Result | Error | Notification | Request) message) -> void

test/server_test.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ def handle_window_show_message_response(title)
828828
end
829829
end
830830

831-
def test_cancelling_requests_returns_nil
831+
def test_cancelling_requests_returns_expected_error_code
832832
uri = URI("file:///foo.rb")
833833

834834
@server.process_message({
@@ -868,8 +868,9 @@ def test_cancelling_requests_returns_nil
868868
mutex.unlock
869869
thread.join
870870

871-
result = find_message(RubyLsp::Result)
872-
assert_nil(result.response)
871+
error = find_message(RubyLsp::Error)
872+
assert_equal(RubyLsp::Constant::ErrorCodes::REQUEST_CANCELLED, error.code)
873+
assert_equal("Request 1 was cancelled", error.message)
873874
end
874875

875876
def test_unsaved_changes_are_indexed_when_computing_automatic_features

0 commit comments

Comments
 (0)