Skip to content

Conversation

@vinistock
Copy link
Member

@vinistock vinistock commented Nov 29, 2024

Motivation

While working on #2938, I noticed that our request cancellation response was incorrect. The spec determines that requests that got cancelled by the client need to return an error using the code for request cancelled (see here and see RequestCancelled here).

Implementation

Started returning an error with the right code, rather than a response with a nil body.

I also started running cancel notifications directly in the main thread without pushing it to the queue, which was another mistake. If we put that notification in the queue, then we're guaranteed to only process them after we finish running requests, which is useless. We need the ability to process them as soon as we receive the notification, so that we have enough time to cancel the requests.

Automated Tests

Added a test to verify this.

Copy link
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@vinistock vinistock added server This pull request should be included in the server gem's release notes bugfix This PR will fix an existing bug labels Nov 29, 2024 — with Graphite App
@vinistock vinistock marked this pull request as ready for review November 29, 2024 19:46
@vinistock vinistock requested a review from a team as a code owner November 29, 2024 19:46
@vinistock vinistock force-pushed the 11-29-use_correct_error_response_for_cancellations branch from 3f7505f to adcfca5 Compare November 29, 2024 21:26
@vinistock vinistock merged commit 0a82b93 into main Nov 29, 2024
35 of 36 checks passed
@vinistock vinistock deleted the 11-29-use_correct_error_response_for_cancellations branch November 29, 2024 21:49
vinistock added a commit that referenced this pull request Jan 8, 2025
### Motivation

This is a temporary revert to fix #3019 while we understand the correct response flow for retried cancelled requests.

This PR reverts the behaviour introduced in #2939, which didn't cause problems for NeoVim.

### Implementation

I didn't perform a full revert of every single change, since we do want to ensure that we're following the spec. I just stopped returning the error and went back to returning a `nil` response until we figure out what's going on.

### Automated Tests

Changed the test so that it asserts the current behaviour. We'll switch it back later.
janko added a commit to janko/ruby-lsp that referenced this pull request Oct 24, 2025
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 Shopify#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 Shopify#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.
janko added a commit to janko/ruby-lsp that referenced this pull request Oct 31, 2025
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 Shopify#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 Shopify#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.
vinistock pushed a commit that referenced this pull request Nov 5, 2025
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix This PR will fix an existing bug server This pull request should be included in the server gem's release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants