Skip to content

Commit db792c5

Browse files
committed
Add a send method to InProcessSourceKitLSPClient and Connection in which the client specifies the request ID
1 parent 086f479 commit db792c5

File tree

6 files changed

+78
-44
lines changed

6 files changed

+78
-44
lines changed

Sources/InProcessClient/InProcessSourceKitLSPClient.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import BuildSystemIntegration
1414
public import Foundation
1515
public import LanguageServerProtocol
1616
import LanguageServerProtocolExtensions
17+
import SKLogging
1718
import SwiftExtensions
1819
import TSCExtensions
1920

@@ -124,11 +125,28 @@ public final class InProcessSourceKitLSPClient: Sendable {
124125
_ request: R,
125126
reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
126127
) -> RequestID {
127-
let requestID = RequestID.number(Int(nextRequestID.fetchAndIncrement()))
128+
let requestID = RequestID.string("sk-\(Int(nextRequestID.fetchAndIncrement()))")
128129
server.handle(request, id: requestID, reply: reply)
129130
return requestID
130131
}
131132

133+
/// Send the request to `server` and return the request result via a completion handler.
134+
///
135+
/// The request ID must not start with `sk-` to avoid conflicting with the request IDs that are created by
136+
/// `send(:reply:)`.
137+
public func send<R: RequestType>(
138+
_ request: R,
139+
id: RequestID,
140+
reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
141+
) {
142+
if case .string(let string) = id {
143+
if string.starts(with: "sk-") {
144+
logger.fault("Manually specified request ID must not have reserved prefix 'sk-'")
145+
}
146+
}
147+
server.handle(request, id: id, reply: reply)
148+
}
149+
132150
/// Send the notification to `server`.
133151
public func send(_ notification: some NotificationType) {
134152
server.handle(notification)

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,36 @@ public protocol Connection: Sendable {
1515
/// Send a notification without a reply.
1616
func send(_ notification: some NotificationType)
1717

18-
/// Send a request and (asynchronously) receive a reply.
18+
/// Generate a new request ID to be used in the `send` method that does not take an explicit request ID.
19+
///
20+
/// These request IDs need to be unique and must not conflict with any request ID that clients might manually specify
21+
/// to `send(_:id:reply:)`.
22+
///
23+
/// To allow, this request IDs starting with `sk-` are reserved to only be generated by this method and are not
24+
/// allowed to be passed directly to `send(_:id:reply:)`. Thus, generating request IDs prefixed with `sk-` here is
25+
/// safe. Similarly returning UUID-based requests IDs is safe because UUIDs are already unique.
26+
func nextRequestID() -> RequestID
27+
28+
/// Send a request with a pre-defined request ID and (asynchronously) receive a reply.
29+
///
30+
/// The request ID must not conflict with any request ID generated by `nextRequestID()`.
1931
func send<Request: RequestType>(
2032
_ request: Request,
33+
id: RequestID,
2134
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
22-
) -> RequestID
35+
)
36+
}
37+
38+
extension Connection {
39+
/// Send a request and (asynchronously) receive a reply.
40+
public func send<Request: RequestType>(
41+
_ request: Request,
42+
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
43+
) -> RequestID {
44+
let id = nextRequestID()
45+
self.send(request, id: id, reply: reply)
46+
return id
47+
}
2348
}
2449

2550
/// An abstract message handler, such as a language server or client.

Sources/LanguageServerProtocolExtensions/LocalConnection.swift

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,15 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#if compiler(>=6)
1413
import Dispatch
15-
package import LanguageServerProtocol
1614
import LanguageServerProtocolJSONRPC
1715
import SKLogging
16+
import SwiftExtensions
17+
18+
#if compiler(>=6)
19+
package import LanguageServerProtocol
1820
#else
19-
import Dispatch
2021
import LanguageServerProtocol
21-
import LanguageServerProtocolJSONRPC
22-
import SKLogging
2322
#endif
2423

2524
/// A connection between two message handlers in the same process.
@@ -45,8 +44,7 @@ package final class LocalConnection: Connection, Sendable {
4544
/// The queue guarding `_nextRequestID`.
4645
private let queue: DispatchQueue = DispatchQueue(label: "local-connection-queue")
4746

48-
/// - Important: Must only be accessed from `queue`
49-
nonisolated(unsafe) private var _nextRequestID: Int = 0
47+
private let _nextRequestID = AtomicUInt32(initialValue: 0)
5048

5149
/// - Important: Must only be accessed from `queue`
5250
nonisolated(unsafe) private var state: State = .ready
@@ -88,11 +86,8 @@ package final class LocalConnection: Connection, Sendable {
8886
}
8987
}
9088

91-
func nextRequestID() -> RequestID {
92-
return queue.sync {
93-
_nextRequestID += 1
94-
return .number(_nextRequestID)
95-
}
89+
public func nextRequestID() -> RequestID {
90+
return .string("sk-\(_nextRequestID.fetchAndIncrement())")
9691
}
9792

9893
package func send<Notification: NotificationType>(_ notification: Notification) {
@@ -110,10 +105,9 @@ package final class LocalConnection: Connection, Sendable {
110105

111106
package func send<Request: RequestType>(
112107
_ request: Request,
108+
id: RequestID,
113109
reply: @Sendable @escaping (LSPResult<Request.Response>) -> Void
114-
) -> RequestID {
115-
let id = nextRequestID()
116-
110+
) {
117111
logger.info(
118112
"""
119113
Sending request to \(self.name, privacy: .public) (id: \(id, privacy: .public)):
@@ -128,7 +122,7 @@ package final class LocalConnection: Connection, Sendable {
128122
"""
129123
)
130124
reply(.failure(.serverCancelled))
131-
return id
125+
return
132126
}
133127

134128
precondition(self.state == .started)
@@ -153,7 +147,5 @@ package final class LocalConnection: Connection, Sendable {
153147
}
154148
reply(result)
155149
}
156-
157-
return id
158150
}
159151
}

Sources/LanguageServerProtocolJSONRPC/JSONRPCConnection.swift

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,7 @@ public final class JSONRPCConnection: Connection {
9797
}
9898

9999
/// An integer that hasn't been used for a request ID yet.
100-
///
101-
/// Access to this must be be guaranteed to be sequential to avoid data races. Currently, all access are
102-
/// - `nextRequestID()`: This is synchronized on `queue`.
103-
private nonisolated(unsafe) var nextRequestIDStorage: Int = 0
100+
let nextRequestIDStorage = AtomicUInt32(initialValue: 0)
104101

105102
struct OutstandingRequest: Sendable {
106103
var responseType: ResponseType.Type
@@ -663,12 +660,8 @@ public final class JSONRPCConnection: Connection {
663660
}
664661

665662
/// Request id for the next outgoing request.
666-
///
667-
/// - Important: Must be called on `queue`
668-
private func nextRequestID() -> RequestID {
669-
dispatchPrecondition(condition: .onQueue(queue))
670-
nextRequestIDStorage += 1
671-
return .number(nextRequestIDStorage)
663+
public func nextRequestID() -> RequestID {
664+
return .string("sk-\(nextRequestIDStorage.fetchAndIncrement())")
672665
}
673666

674667
// MARK: Connection interface
@@ -691,14 +684,13 @@ public final class JSONRPCConnection: Connection {
691684
/// When the receiving end replies to the request, execute `reply` with the response.
692685
public func send<Request: RequestType>(
693686
_ request: Request,
687+
id: RequestID,
694688
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
695-
) -> RequestID {
696-
let id: RequestID = self.queue.sync {
697-
let id = nextRequestID()
698-
689+
) {
690+
self.queue.sync {
699691
guard readyToSend() else {
700692
reply(.failure(.serverCancelled))
701-
return id
693+
return
702694
}
703695

704696
outstandingRequests[id] = OutstandingRequest(
@@ -734,10 +726,8 @@ public final class JSONRPCConnection: Connection {
734726
)
735727

736728
send(.request(request, id: id))
737-
return id
729+
return
738730
}
739-
740-
return id
741731
}
742732

743733
/// After the remote side of the connection sent a request to us, return a reply to the remote side.

Sources/SKTestSupport/DummyBuildSystemManagerConnectionToClient.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ package struct DummyBuildSystemManagerConnectionToClient: BuildSystemManagerConn
2929

3030
package func send(_ notification: some LanguageServerProtocol.NotificationType) {}
3131

32+
package func nextRequestID() -> RequestID {
33+
return .string(UUID().uuidString)
34+
}
35+
3236
package func send<Request: RequestType>(
3337
_ request: Request,
38+
id: RequestID,
3439
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
35-
) -> RequestID {
40+
) {
3641
reply(.failure(ResponseError.unknown("Not implemented")))
37-
return .string(UUID().uuidString)
3842
}
3943

4044
package func watchFiles(_ fileWatchers: [LanguageServerProtocol.FileSystemWatcher]) async {}

Sources/SourceKitLSP/Workspace.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,17 +230,22 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
230230
sourceKitLSPServer.sendNotificationToClient(notification)
231231
}
232232

233+
func nextRequestID() -> RequestID {
234+
return .string(UUID().uuidString)
235+
}
236+
233237
func send<Request: RequestType>(
234238
_ request: Request,
239+
id: RequestID,
235240
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
236-
) -> RequestID {
241+
) {
237242
guard let sourceKitLSPServer else {
238243
// `SourceKitLSPServer` has been destructed. We are tearing down the
239244
// language server. Nothing left to do.
240245
reply(.failure(ResponseError.unknown("Connection to the editor closed")))
241-
return .string(UUID().uuidString)
246+
return
242247
}
243-
return sourceKitLSPServer.client.send(request, reply: reply)
248+
sourceKitLSPServer.client.send(request, id: id, reply: reply)
244249
}
245250

246251
/// Whether the client can handle `WorkDoneProgress` requests.

0 commit comments

Comments
 (0)