Skip to content

Commit 1f75c15

Browse files
committed
[mlir-lsp] Support outgoing requests
Add support for outgoing requests to `lsp::MessageHandler`. Much like `MessageHandler::outgoingNotification`, this allows for the message handler to send outgoing messages via its JSON transport, but in this case, those messages are requests, not notifications. Requests receive responses (also referred to as "replies" in `MLIRLspServerSupportLib`). These were previously unsupported, and `lsp::MessageHandler` would log an error each time it processed a JSON message that appeared to be a response (something with an "id" field, but no "method" field). However, the `outgoingRequest` method now handles response callbacks: an outgoing request with a given ID is set up such that a callback function is invoked when a response with that ID is received.
1 parent b77416e commit 1f75c15

File tree

3 files changed

+69
-15
lines changed

3 files changed

+69
-15
lines changed

mlir/include/mlir/Tools/lsp-server-support/Transport.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H
1616
#define MLIR_TOOLS_LSPSERVERSUPPORT_TRANSPORT_H
1717

18+
#include "mlir/Support/DebugStringHelper.h"
1819
#include "mlir/Support/LLVM.h"
1920
#include "mlir/Support/LogicalResult.h"
2021
#include "mlir/Tools/lsp-server-support/Logging.h"
@@ -171,6 +172,24 @@ class MessageHandler {
171172
};
172173
}
173174

175+
/// Create an OutgoingMessage function that, when called, sends a request with
176+
/// the given method and ID via the transport. Should the outgoing request be
177+
/// met with a response, the response callback is invoked to handle that
178+
/// response.
179+
template <typename T>
180+
OutgoingMessage<T> outgoingRequest(
181+
llvm::StringLiteral method, llvm::json::Value id,
182+
llvm::unique_function<void(llvm::Expected<llvm::json::Value>)> callback) {
183+
responseHandlers.insert(
184+
{debugString(id), std::make_pair(method.str(), std::move(callback))});
185+
186+
return [&, method, id](const T &params) {
187+
std::lock_guard<std::mutex> transportLock(transportOutputMutex);
188+
Logger::info("--> {0}", method);
189+
transport.call(method, llvm::json::Value(params), id);
190+
};
191+
}
192+
174193
private:
175194
template <typename HandlerT>
176195
using HandlerMap = llvm::StringMap<llvm::unique_function<HandlerT>>;
@@ -179,6 +198,14 @@ class MessageHandler {
179198
HandlerMap<void(llvm::json::Value, Callback<llvm::json::Value>)>
180199
methodHandlers;
181200

201+
/// A pair of (1) the original request's method name, and (2) the callback
202+
/// function to be invoked for responses.
203+
using ResponseHandlerTy =
204+
std::pair<std::string,
205+
llvm::unique_function<void(llvm::Expected<llvm::json::Value>)>>;
206+
/// A mapping from request/response ID to response handler.
207+
llvm::StringMap<ResponseHandlerTy> responseHandlers;
208+
182209
JSONTransport &transport;
183210

184211
/// Mutex to guard sending output messages to the transport.

mlir/lib/Tools/lsp-server-support/Transport.cpp

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,17 @@ bool MessageHandler::onCall(llvm::StringRef method, llvm::json::Value params,
117117

118118
bool MessageHandler::onReply(llvm::json::Value id,
119119
llvm::Expected<llvm::json::Value> result) {
120-
// TODO: Add support for reply callbacks when support for outgoing messages is
121-
// added. For now, we just log an error on any replies received.
122-
Callback<llvm::json::Value> replyHandler =
123-
[&id](llvm::Expected<llvm::json::Value> result) {
124-
Logger::error(
125-
"received a reply with ID {0}, but there was no such call", id);
126-
if (!result)
127-
llvm::consumeError(result.takeError());
128-
};
129-
130-
// Log and run the reply handler.
131-
if (result)
132-
replyHandler(std::move(result));
133-
else
134-
replyHandler(result.takeError());
120+
auto it = responseHandlers.find(debugString(id));
121+
if (it != responseHandlers.end()) {
122+
Logger::info("--> reply:{0}({1})", it->second.first, id);
123+
it->second.second(std::move(result));
124+
} else {
125+
Logger::error(
126+
"received a reply with ID {0}, but there was no such outgoing request",
127+
id);
128+
if (!result)
129+
llvm::consumeError(result.takeError());
130+
}
135131
return true;
136132
}
137133

mlir/unittests/Tools/lsp-server-support/Transport.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,35 @@ TEST_F(TransportInputTest, OutgoingNotification) {
125125
notifyFn(CompletionList{});
126126
EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-notification\""));
127127
}
128+
129+
TEST_F(TransportInputTest, ResponseHandlerNotFound) {
130+
// Unhandled responses are only reported via error logging. As a result, this
131+
// test can't make any expectations -- but it prints the output anyway, by way
132+
// of demonstration.
133+
Logger::setLogLevel(Logger::Level::Error);
134+
writeInput("{\"jsonrpc\":\"2.0\",\"id\":81,\"params\":null}\n");
135+
runTransport();
136+
}
137+
138+
TEST_F(TransportInputTest, OutgoingRequest) {
139+
Logger::setLogLevel(Logger::Level::Debug);
140+
141+
// Make an outgoing request.
142+
bool responseCallbackInvoked = false;
143+
auto callFn = getMessageHandler().outgoingRequest<CompletionList>(
144+
"outgoing-request", 82,
145+
[&responseCallbackInvoked](llvm::Expected<llvm::json::Value> value) {
146+
ASSERT_TRUE((bool)value);
147+
responseCallbackInvoked = true;
148+
});
149+
callFn(CompletionList{});
150+
EXPECT_THAT(getOutput(), HasSubstr("\"method\":\"outgoing-request\""));
151+
EXPECT_FALSE(responseCallbackInvoked);
152+
153+
// The request receives a response. The message handler handles this response
154+
// by invoking the callback from above.
155+
writeInput("{\"jsonrpc\":\"2.0\",\"id\":82,\"params\":null}\n");
156+
runTransport();
157+
EXPECT_TRUE(responseCallbackInvoked);
158+
}
128159
} // namespace

0 commit comments

Comments
 (0)