mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 20:15:20 +00:00
preemptive fix in rust-analyzer: prevent indefinite startup hang and harden init (#1680)
Hanging was never reproduced, this is preemptive hardening - Add a 120s timeout to the server_ready wait (previously waited with no timeout, so a server that crashed or never sent experimental/serverStatus with quiescent=true would hang startup forever). On timeout we log a warning and proceed, matching kotlin/matlab/powershell/etc. - Harden the language/status and experimental/serverStatus notification handlers to use dict.get() instead of raising KeyError on unexpected payloads (a KeyError in the quiescent handler could itself prevent server_ready from ever being set, feeding the hang). - Relax the over-strict completionProvider capability check: an exact-dict equality assertion crashed initialization whenever rust-analyzer changed or added a completionProvider field. These values are not used downstream, so a mismatch is now logged as a warning instead of asserted.
This commit is contained in:
1 parent
b235c45389
commit
837e1c2496
1 file changed
+24
-13
@@ -676,9 +676,8 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
"""
|
||||
|
||||
def register_capability_handler(params: dict) -> None:
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
for registration in params.get("registrations", []):
|
||||
if registration.get("method") == "workspace/executeCommand":
|
||||
self.initialize_searcher_command_available.set()
|
||||
self.resolve_main_method_available.set()
|
||||
return
|
||||
@@ -687,7 +686,7 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
if params.get("type") == "ServiceReady" and params.get("message") == "ServiceReady":
|
||||
self.service_ready_event.set()
|
||||
|
||||
def execute_client_command_handler(params: dict) -> list:
|
||||
@@ -697,7 +696,7 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
return
|
||||
|
||||
def check_experimental_status(params: dict) -> None:
|
||||
if params["quiescent"] == True:
|
||||
if params.get("quiescent") is True:
|
||||
self.server_ready.set()
|
||||
|
||||
def window_log_message(msg: dict) -> None:
|
||||
@@ -718,13 +717,25 @@ class RustAnalyzer(SolidLanguageServer):
|
||||
|
||||
log.info("Sending initialize request from LSP client to LSP server and awaiting response")
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2 # type: ignore
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"resolveProvider": True,
|
||||
"triggerCharacters": [":", ".", "'", "("],
|
||||
"completionItem": {"labelDetailsSupport": True},
|
||||
}
|
||||
# Validate key server capabilities. These are sanity checks, not hard requirements:
|
||||
# rust-analyzer may evolve its advertised capabilities across versions, so a mismatch is
|
||||
# logged rather than asserted. (An over-strict equality check on completionProvider here
|
||||
# previously crashed startup whenever upstream added or changed a completionProvider field.)
|
||||
capabilities = init_response.get("capabilities", {}) if isinstance(init_response, dict) else {}
|
||||
text_document_sync = capabilities.get("textDocumentSync")
|
||||
change_kind = text_document_sync.get("change") if isinstance(text_document_sync, dict) else text_document_sync
|
||||
if change_kind != 2:
|
||||
log.warning("rust-analyzer: unexpected textDocumentSync.change=%r (expected 2 = incremental)", change_kind)
|
||||
if "completionProvider" not in capabilities:
|
||||
log.warning("rust-analyzer: server did not advertise a completionProvider capability")
|
||||
self.server.notify.initialized({})
|
||||
|
||||
self.server_ready.wait()
|
||||
# Wait for rust-analyzer to finish initial indexing (it emits experimental/serverStatus with
|
||||
# quiescent=true when ready). Use a timeout so a server that crashes or never signals
|
||||
# readiness cannot hang startup indefinitely (previously this waited with no timeout).
|
||||
_SERVER_READY_TIMEOUT = 120.0
|
||||
log.info("Waiting for rust-analyzer to signal readiness (quiescent)...")
|
||||
if self.server_ready.wait(timeout=_SERVER_READY_TIMEOUT):
|
||||
log.info("rust-analyzer ready")
|
||||
else:
|
||||
log.warning("rust-analyzer did not signal readiness within %.0fs; proceeding anyway", _SERVER_READY_TIMEOUT)
|
||||
Reference in new issue
Block a user