Skip to content

Commit 632113a

Browse files
committed
Add custom request to return LSP internal state (#3194)
### Motivation Diagnosing concurrency issues in the LSP tends to be very difficult and without more detailed information about the state of the language server, it becomes extra challenging to understand what's going on. Let's add a command to surface internal state information, which we can hook to a command in the extension. When users manage to reproduce the problem, they can invoke the command and provide richer details. ### Implementation The idea is to return internal state information that may be relevant to diagnose a corrupt state or crash. For now, I included the state of the worker, backtrace, size of the incoming queue and all stored documents. This should hopefully help us understand the following things: 1. Is the worker dead or just stuck? 2. If it's stuck, where is it stuck? Is the queue increasing causing the worker to get backlogged? 3. Why did we get stuck? Are the documents we stored in an out of sync state with the client? ### Automated Tests Added a test.
1 parent 1347d79 commit 632113a

File tree

3 files changed

+44
-1
lines changed

3 files changed

+44
-1
lines changed

lib/ruby_lsp/base_server.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def start
9090
# The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
9191
# else is pushed into the incoming queue
9292
case method
93-
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
93+
when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
94+
"rubyLsp/diagnoseState"
9495
process_message(message)
9596
when "shutdown"
9697
@global_state.synchronize do

lib/ruby_lsp/server.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def process_message(message)
108108
)
109109
when "rubyLsp/composeBundle"
110110
compose_bundle(message)
111+
when "rubyLsp/diagnoseState"
112+
diagnose_state(message)
111113
when "$/cancelRequest"
112114
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
113115
when nil
@@ -1368,5 +1370,24 @@ def compose_bundle(message)
13681370
end
13691371
end
13701372
end
1373+
1374+
# Returns internal state information for debugging purposes
1375+
sig { params(message: T::Hash[Symbol, T.untyped]).void }
1376+
def diagnose_state(message)
1377+
documents = {}
1378+
@store.each { |uri, document| documents[uri] = document.source }
1379+
1380+
send_message(
1381+
Result.new(
1382+
id: message[:id],
1383+
response: {
1384+
workerAlive: @worker.alive?,
1385+
backtrace: @worker.backtrace,
1386+
documents: documents,
1387+
incomingQueueSize: @incoming_queue.length,
1388+
},
1389+
),
1390+
)
1391+
end
13711392
end
13721393
end

test/server_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,6 +1371,27 @@ class Foo
13711371
end
13721372
end
13731373

1374+
def test_diagnose_state
1375+
@server.process_message({
1376+
method: "textDocument/didOpen",
1377+
params: {
1378+
textDocument: {
1379+
uri: URI::Generic.from_path(path: "/foo.rb"),
1380+
text: "class Foo\nend",
1381+
version: 1,
1382+
languageId: "ruby",
1383+
},
1384+
},
1385+
})
1386+
@server.process_message({ id: 1, method: "rubyLsp/diagnoseState", params: {} })
1387+
result = find_message(RubyLsp::Result, id: 1)
1388+
1389+
assert(result.response[:workerAlive])
1390+
assert_equal({ "file:///foo.rb" => "class Foo\nend" }, result.response[:documents])
1391+
assert(result.response.key?(:backtrace))
1392+
assert_equal(0, result.response[:incomingQueueSize])
1393+
end
1394+
13741395
private
13751396

13761397
def with_uninstalled_rubocop(&block)

0 commit comments

Comments
 (0)