🐛 fix(core): dispatch cancellable requests asynchronously

This commit is contained in:
csh
2026-07-14 08:56:30 +08:00
parent efb6eae796
commit 101b69e84f
4 changed files with 387 additions and 11 deletions
+90 -3
View File
@@ -184,7 +184,6 @@ def assert_json_rpc_errors(server: Path) -> None:
"method": "initialize",
"params": {},
}),
frame({"jsonrpc": "2.0", "id": 9, "method": "missing/method"}),
frame({"jsonrpc": "2.0", "id": 10, "method": "shutdown"}),
frame({"jsonrpc": "2.0", "method": "exit"}),
])
@@ -204,8 +203,95 @@ def assert_json_rpc_errors(server: Path) -> None:
raise RuntimeError("A non-object JSON value should return InvalidRequest")
if response_by_id(result.stdout, 7).get("error", {}).get("code") != -32600:
raise RuntimeError("jsonrpc other than 2.0 should return InvalidRequest")
if response_by_id(result.stdout, 9).get("error", {}).get("code") != -32601:
raise RuntimeError("Unknown request method should return MethodNotFound")
client = LspClient(server)
try:
client.send({
"jsonrpc": "2.0",
"id": 11,
"method": "initialize",
"params": {},
})
client.read()
client.send({"jsonrpc": "2.0", "id": 9, "method": "missing/method"})
if client.read().get("error", {}).get("code") != -32601:
raise RuntimeError("Unknown request method should return MethodNotFound")
client.send({"jsonrpc": "2.0", "id": 12, "method": "shutdown"})
client.read()
client.send({"jsonrpc": "2.0", "method": "exit"})
if client.close_input() != 0:
raise RuntimeError("unknown method sequence should shut down cleanly")
finally:
client.kill()
def assert_cancellation_and_failures(server: Path) -> None:
client = LspClient(server)
try:
client.send({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {},
})
if client.read().get("id") != 1:
raise RuntimeError("missing initialize response")
client.send({"jsonrpc": "2.0", "id": 2, "method": "test/block"})
client.send({"jsonrpc": "2.0", "id": "2", "method": "test/block"})
client.send({
"jsonrpc": "2.0",
"method": "$/cancelRequest",
"params": {"id": 2},
})
cancelled = client.read(timeout=1)
if cancelled.get("id") != 2 or \
cancelled.get("error", {}).get("code") != -32800:
raise RuntimeError("integer request id should be cancelled independently")
client.send({
"jsonrpc": "2.0",
"method": "$/cancelRequest",
"params": {"id": "2"},
})
string_cancelled = client.read(timeout=1)
if string_cancelled.get("id") != "2" or \
string_cancelled.get("error", {}).get("code") != -32800:
raise RuntimeError("string request id should be cancelled independently")
client.send({"jsonrpc": "2.0", "id": 3, "method": "test/throw"})
failed = client.read(timeout=1)
if failed.get("id") != 3 or \
failed.get("error", {}).get("code") != -32603:
raise RuntimeError("request exceptions should return InternalError")
client.send({"jsonrpc": "2.0", "id": 4, "method": "test/block"})
client.send({"jsonrpc": "2.0", "id": 4, "method": "test/block"})
duplicate = client.read(timeout=1)
if duplicate.get("id") != 4 or \
duplicate.get("error", {}).get("code") != -32600:
raise RuntimeError("duplicate active request id should return InvalidRequest")
client.send({
"jsonrpc": "2.0",
"method": "$/cancelRequest",
"params": {"id": 4},
})
cancelled_original = client.read(timeout=1)
if cancelled_original.get("id") != 4 or \
cancelled_original.get("error", {}).get("code") != -32800:
raise RuntimeError("duplicate id must not replace the original request")
client.send({"jsonrpc": "2.0", "method": "test/throwNotification"})
client.send({"jsonrpc": "2.0", "id": 5, "method": "shutdown"})
shutdown = client.read(timeout=1)
if shutdown.get("id") != 5 or shutdown.get("result", "missing") is not None:
raise RuntimeError("notification exception should not stop the server")
client.send({"jsonrpc": "2.0", "method": "exit"})
if client.close_input() != 0:
raise RuntimeError("fixture should exit cleanly after cancellation tests")
finally:
client.kill()
def main() -> int:
@@ -215,6 +301,7 @@ def main() -> int:
assert_lifecycle(args.server)
assert_json_rpc_errors(args.server)
assert_cancellation_and_failures(args.server)
return 0