🐛 fix(core): dispatch cancellable requests asynchronously
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,77 @@ namespace lsp::test::provider
|
||||
}
|
||||
};
|
||||
|
||||
class FixtureBlock final : public core::IRequestProvider
|
||||
{
|
||||
public:
|
||||
std::string GetMethod() const override
|
||||
{
|
||||
return "test/block";
|
||||
}
|
||||
|
||||
std::string GetProviderName() const override
|
||||
{
|
||||
return "FixtureBlock";
|
||||
}
|
||||
|
||||
std::string ProvideResponse(const protocol::RequestMessage& request,
|
||||
core::ExecutionContext& context) override
|
||||
{
|
||||
const auto deadline =
|
||||
std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (!context.GetStopToken().stop_requested() &&
|
||||
std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
protocol::ResponseMessage response;
|
||||
response.id = request.id;
|
||||
response.result = protocol::LSPAny(protocol::string("completed"));
|
||||
return codec::Serialize(response).value();
|
||||
}
|
||||
};
|
||||
|
||||
class FixtureThrow final : public core::IRequestProvider
|
||||
{
|
||||
public:
|
||||
std::string GetMethod() const override
|
||||
{
|
||||
return "test/throw";
|
||||
}
|
||||
|
||||
std::string GetProviderName() const override
|
||||
{
|
||||
return "FixtureThrow";
|
||||
}
|
||||
|
||||
std::string ProvideResponse(const protocol::RequestMessage&,
|
||||
core::ExecutionContext&) override
|
||||
{
|
||||
throw std::runtime_error("fixture request failure");
|
||||
}
|
||||
};
|
||||
|
||||
class FixtureThrowNotification final : public core::INotificationProvider
|
||||
{
|
||||
public:
|
||||
std::string GetMethod() const override
|
||||
{
|
||||
return "test/throwNotification";
|
||||
}
|
||||
|
||||
std::string GetProviderName() const override
|
||||
{
|
||||
return "FixtureThrowNotification";
|
||||
}
|
||||
|
||||
void HandleNotification(const protocol::NotificationMessage&,
|
||||
core::ExecutionContext&) override
|
||||
{
|
||||
throw std::runtime_error("fixture notification failure");
|
||||
}
|
||||
};
|
||||
|
||||
int RunCoreServerFixture()
|
||||
{
|
||||
spdlog::set_level(spdlog::level::off);
|
||||
@@ -48,6 +119,10 @@ namespace lsp::test::provider
|
||||
std::cout,
|
||||
[](core::RequestDispatcher& dispatcher) {
|
||||
dispatcher.RegisterRequestProvider(std::make_shared<FixtureInitialize>());
|
||||
dispatcher.RegisterRequestProvider(std::make_shared<FixtureBlock>());
|
||||
dispatcher.RegisterRequestProvider(std::make_shared<FixtureThrow>());
|
||||
dispatcher.RegisterNotificationProvider(
|
||||
std::make_shared<FixtureThrowNotification>());
|
||||
},
|
||||
2,
|
||||
"");
|
||||
|
||||
Reference in New Issue
Block a user