Skip to content

Commit 1f02b95

Browse files
committed
Shift responsibility for in-order message handling from Connection to SourceKitServer
This generally seems like the cleaner design because `SourceKitServer` is actually able to semantically inspect the message and decide whether it can be handled concurrently with other requests.
1 parent edfda7d commit 1f02b95

File tree

6 files changed

+215
-181
lines changed

6 files changed

+215
-181
lines changed

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ public protocol MessageHandler: AnyObject {
4646
/// The method should return as soon as the notification has been sufficiently
4747
/// handled to avoid out-of-order requests, e.g. once the notification has
4848
/// been forwarded to clangd.
49-
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async
49+
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier)
5050

5151
/// Handle a request and (asynchronously) receive a reply.
5252
///
5353
/// The method should return as soon as the request has been sufficiently
5454
/// handled to avoid out-of-order requests, e.g. once the corresponding
5555
/// request has been sent to sourcekitd. The actual semantic computation
5656
/// should occur after the method returns and report the result via `reply`.
57-
func handle<Request: RequestType>(_ params: Request, id: RequestID, from clientID: ObjectIdentifier, reply: @escaping (LSPResult<Request.Response>) -> Void) async
57+
func handle<Request: RequestType>(_ params: Request, id: RequestID, from clientID: ObjectIdentifier, reply: @escaping (LSPResult<Request.Response>) -> Void)
5858
}
5959

6060
/// A connection between two message handlers in the same process.
@@ -77,18 +77,7 @@ public final class LocalConnection {
7777

7878
/// The queue guarding `_nextRequestID`.
7979
let queue: DispatchQueue = DispatchQueue(label: "local-connection-queue")
80-
81-
/// The queue on which all messages (notifications, requests, responses) are
82-
/// handled.
83-
///
84-
/// The queue is blocked until the message has been sufficiently handled to
85-
/// avoid out-of-order handling of messages. For sourcekitd, this means that
86-
/// a request has been sent to sourcekitd and for clangd, this means that we
87-
/// have forwarded the request to clangd.
88-
///
89-
/// The actual semantic handling of the message happens off this queue.
90-
let messageHandlingQueue: AsyncQueue = AsyncQueue(.serial)
91-
80+
9281
var _nextRequestID: Int = 0
9382

9483
var state: State = .ready
@@ -125,9 +114,7 @@ public final class LocalConnection {
125114

126115
extension LocalConnection: Connection {
127116
public func send<Notification>(_ notification: Notification) where Notification: NotificationType {
128-
messageHandlingQueue.async {
129-
await self.handler?.handle(notification, from: ObjectIdentifier(self))
130-
}
117+
self.handler?.handle(notification, from: ObjectIdentifier(self))
131118
}
132119

133120
public func send<Request: RequestType>(
@@ -137,19 +124,17 @@ extension LocalConnection: Connection {
137124
) -> RequestID {
138125
let id = nextRequestID()
139126

140-
messageHandlingQueue.async {
141-
guard let handler = self.handler else {
142-
queue.async {
143-
reply(.failure(.serverCancelled))
144-
}
145-
return
127+
guard let handler = self.handler else {
128+
queue.async {
129+
reply(.failure(.serverCancelled))
146130
}
131+
return id
132+
}
147133

148-
precondition(self.state == .started)
149-
await handler.handle(request, id: id, from: ObjectIdentifier(self)) { result in
150-
queue.async {
151-
reply(result)
152-
}
134+
precondition(self.state == .started)
135+
handler.handle(request, id: id, from: ObjectIdentifier(self)) { result in
136+
queue.async {
137+
reply(result)
153138
}
154139
}
155140

Sources/LanguageServerProtocol/Message.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public protocol _RequestType: MessageType {
2828
id: RequestID,
2929
connection: Connection,
3030
reply: @escaping (LSPResult<ResponseType>, RequestID) -> Void
31-
) async
31+
)
3232
}
3333

3434
/// A request, which must have a unique `method` name as well as an associated response type.
@@ -54,16 +54,16 @@ extension RequestType {
5454
id: RequestID,
5555
connection: Connection,
5656
reply: @escaping (LSPResult<ResponseType>, RequestID) -> Void
57-
) async {
58-
await handler.handle(self, id: id, from: ObjectIdentifier(connection)) { response in
57+
) {
58+
handler.handle(self, id: id, from: ObjectIdentifier(connection)) { response in
5959
reply(response.map({ $0 as ResponseType }), id)
6060
}
6161
}
6262
}
6363

6464
extension NotificationType {
65-
public func _handle(_ handler: MessageHandler, connection: Connection) async {
66-
await handler.handle(self, from: ObjectIdentifier(connection))
65+
public func _handle(_ handler: MessageHandler, connection: Connection) {
66+
handler.handle(self, from: ObjectIdentifier(connection))
6767
}
6868
}
6969

Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,6 @@ public final class JSONRPCConnection {
3131
/// The queue on which we send data.
3232
let sendQueue: DispatchQueue = DispatchQueue(label: "jsonrpc-send-queue", qos: .userInitiated)
3333

34-
/// The queue on which all messages (notifications, requests, responses) are
35-
/// handled.
36-
///
37-
/// The queue is blocked until the message has been sufficiently handled to
38-
/// avoid out-of-order handling of messages. For sourcekitd, this means that
39-
/// a request has been sent to sourcekitd and for clangd, this means that we
40-
/// have forwarded the request to clangd.
41-
///
42-
/// The actual semantic handling of the message happens off this queue.
43-
let messageHandlingQueue: AsyncQueue = AsyncQueue(.serial)
4434
let receiveIO: DispatchIO
4535
let sendIO: DispatchIO
4636
let messageRegistry: MessageRegistry
@@ -282,17 +272,14 @@ public final class JSONRPCConnection {
282272
func handle(_ message: JSONRPCMessage) {
283273
switch message {
284274
case .notification(let notification):
285-
messageHandlingQueue.async {
286-
await notification._handle(self.receiveHandler!, connection: self)
287-
}
275+
notification._handle(self.receiveHandler!, connection: self)
288276
case .request(let request, id: let id):
289277
let semaphore: DispatchSemaphore? = syncRequests ? .init(value: 0) : nil
290-
messageHandlingQueue.async {
291-
await request._handle(self.receiveHandler!, id: id, connection: self) { (response, id) in
292-
self.sendReply(response, id: id)
293-
semaphore?.signal()
294-
}
278+
request._handle(self.receiveHandler!, id: id, connection: self) { (response, id) in
279+
self.sendReply(response, id: id)
280+
semaphore?.signal()
295281
}
282+
296283
semaphore?.wait()
297284

298285
case .response(let response, id: let id):

Sources/SKCore/BuildServerBuildSystem.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ public actor BuildServerBuildSystem: MessageHandler {
5151

5252
var buildServer: JSONRPCConnection?
5353

54+
/// The queue on which all messages that originate from the build server are
55+
/// handled.
56+
///
57+
/// These are requests and notifications sent *from* the build server,
58+
/// not replies from the build server.
59+
///
60+
/// This ensures that messages from the build server are handled in the order
61+
/// they were received. Swift concurrency does not guarentee in-order
62+
/// execution of tasks.
63+
public let bspMessageHandlingQueue = AsyncQueue(.serial)
64+
5465
let searchPaths: [AbsolutePath]
5566

5667
public private(set) var indexDatabasePath: AbsolutePath?
@@ -167,18 +178,20 @@ public actor BuildServerBuildSystem: MessageHandler {
167178
/// the build server has sent us a notification.
168179
///
169180
/// We need to notify the delegate about any updated build settings.
170-
public func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
171-
if let params = params as? BuildTargetsChangedNotification {
172-
await self.handleBuildTargetsChanged(Notification(params, clientID: clientID))
173-
} else if let params = params as? FileOptionsChangedNotification {
174-
await self.handleFileOptionsChanged(Notification(params, clientID: clientID))
181+
public nonisolated func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) {
182+
bspMessageHandlingQueue.async {
183+
if let params = params as? BuildTargetsChangedNotification {
184+
await self.handleBuildTargetsChanged(Notification(params, clientID: clientID))
185+
} else if let params = params as? FileOptionsChangedNotification {
186+
await self.handleFileOptionsChanged(Notification(params, clientID: clientID))
187+
}
175188
}
176189
}
177190

178191
/// Handler for requests received **from** the build server.
179192
///
180193
/// We currently can't handle any requests sent from the build server to us.
181-
public func handle<R: RequestType>(
194+
public nonisolated func handle<R: RequestType>(
182195
_ params: R,
183196
id: RequestID,
184197
from clientID: ObjectIdentifier,

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
4545
/// The queue on which clangd calls us back.
4646
public let clangdCommunicationQueue: DispatchQueue = DispatchQueue(label: "language-server-queue", qos: .userInitiated)
4747

48+
/// The queue on which all messages that originate from clangd are handled.
49+
///
50+
/// This includes requests and notifications sent *from* clangd and does not
51+
/// include replies from clangd.
52+
///
53+
/// These are requests and notifications sent *from* clangd, not replies from
54+
/// clangd.
55+
///
56+
/// Since we are blindly forwarding requests from clangd to the editor, we
57+
/// cannot allow concurrent requests. This should be fine since the number of
58+
/// requests and notifications sent from clangd to the client is quite small.
59+
public let clangdMessageHandlingQueue = AsyncQueue(.serial)
60+
4861
/// The ``SourceKitServer`` instance that created this `ClangLanguageServerShim`.
4962
///
5063
/// Used to send requests and notifications to the editor.
@@ -255,41 +268,41 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
255268
/// sending a notification that's intended for the editor.
256269
///
257270
/// We should either handle it ourselves or forward it to the editor.
258-
func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
259-
switch params {
260-
case let publishDiags as PublishDiagnosticsNotification:
261-
await self.publishDiagnostics(Notification(publishDiags, clientID: clientID))
262-
default:
263-
// We don't know how to handle any other notifications and ignore them.
264-
log("Ignoring unknown notification \(type(of: params))", level: .warning)
265-
break
271+
nonisolated func handle(_ params: some NotificationType, from clientID: ObjectIdentifier) {
272+
clangdMessageHandlingQueue.async {
273+
switch params {
274+
case let publishDiags as PublishDiagnosticsNotification:
275+
await self.publishDiagnostics(Notification(publishDiags, clientID: clientID))
276+
default:
277+
// We don't know how to handle any other notifications and ignore them.
278+
log("Ignoring unknown notification \(type(of: params))", level: .warning)
279+
break
280+
}
266281
}
267282
}
268283

269284
/// Handler for requests received **from** clangd, ie. **clangd** is
270285
/// sending a notification that's intended for the editor.
271286
///
272287
/// We should either handle it ourselves or forward it to the client.
273-
func handle<R: RequestType>(
288+
nonisolated func handle<R: RequestType>(
274289
_ params: R,
275290
id: RequestID,
276291
from clientID: ObjectIdentifier,
277292
reply: @escaping (LSPResult<R.Response>) -> Void
278-
) async {
279-
let request = Request(params, id: id, clientID: clientID, cancellation: CancellationToken(), reply: { result in
280-
reply(result)
281-
})
282-
guard let sourceKitServer else {
283-
// `SourceKitServer` has been destructed. We are tearing down the language
284-
// server. Nothing left to do.
285-
request.reply(.failure(.unknown("Connection to the editor closed")))
286-
return
287-
}
293+
) {
294+
clangdMessageHandlingQueue.async {
295+
let request = Request(params, id: id, clientID: clientID, cancellation: CancellationToken(), reply: { result in
296+
reply(result)
297+
})
298+
guard let sourceKitServer = await self.sourceKitServer else {
299+
// `SourceKitServer` has been destructed. We are tearing down the language
300+
// server. Nothing left to do.
301+
request.reply(.failure(.unknown("Connection to the editor closed")))
302+
return
303+
}
288304

289-
if request.clientID == ObjectIdentifier(self.clangd) {
290305
await sourceKitServer.sendRequestToClient(request.params, reply: request.reply)
291-
} else {
292-
request.reply(.failure(ResponseError.methodNotFound(R.method)))
293306
}
294307
}
295308

0 commit comments

Comments
 (0)