Skip to content

Commit 1f39d7b

Browse files
authored
Add base support for async requests (#53)
1 parent 32aaa89 commit 1f39d7b

File tree

5 files changed

+76
-19
lines changed

5 files changed

+76
-19
lines changed

Sources/SourceKitBazelBSP/RequestHandlers/InitializeHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ final class InitializeHandler {
5858

5959
func initializeBuild(
6060
_ request: InitializeBuildRequest,
61-
_ id: RequestID
61+
_ id: RequestID,
6262
) throws -> (InitializeBuildResponse, InitializedServerConfig) {
6363
let taskId = TaskId(id: "initializeBuild-\(id.description)")
6464
connection?.startWorkTask(id: taskId, title: "Indexing: Initializing sourcekit-bazel-bsp")

Sources/SourceKitBazelBSP/Server/MessageHandler/BSPMessageHandler.swift

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ final class BSPMessageHandler: MessageHandler {
4747
state.notificationHandlers[Notification.method] = AnyNotificationHandler(handler: notificationHandler)
4848
}
4949

50+
/// Simple abstraction for registering requests that don't need async responses.
51+
func register<Request: RequestType>(syncRequestHandler: @escaping BSPSyncRequestHandler<Request>) {
52+
register(requestHandler: { (request: Request, id, completion) in
53+
do {
54+
let response = try syncRequestHandler(request, id)
55+
completion(.success(response))
56+
} catch {
57+
completion(.failure(error))
58+
}
59+
})
60+
}
61+
5062
func handle<Notification: NotificationType>(_ notification: Notification) {
5163
logger.info("Received notification: \(Notification.method)")
5264
do {
@@ -63,16 +75,19 @@ final class BSPMessageHandler: MessageHandler {
6375
logger.info("Received request: \(Request.method)")
6476
do {
6577
let handler = try getHandler(for: request, id, reply, state: state)
66-
let response = try handler(request, id)
67-
logger.info("Replying to \(Request.method)")
68-
reply(.success(response))
78+
handler(request, id) { [buildLSPError] result in
79+
do {
80+
let response = try result.get()
81+
logger.info("Replying to \(Request.method)")
82+
reply(.success(response))
83+
} catch {
84+
logger.error("Error while replying to \(Request.method): \(error.localizedDescription)")
85+
reply(.failure(buildLSPError(error)))
86+
}
87+
}
6988
} catch {
7089
logger.error("Error while handling BSP request: \(error.localizedDescription)")
71-
if let responseError = error as? ResponseError {
72-
reply(.failure(responseError))
73-
} else {
74-
reply(.failure(ResponseError.internalError(error.localizedDescription)))
75-
}
90+
reply(.failure(buildLSPError(from: error)))
7691
}
7792
}
7893

@@ -105,4 +120,11 @@ final class BSPMessageHandler: MessageHandler {
105120
}
106121
return handler
107122
}
123+
124+
private func buildLSPError(from error: Error) -> ResponseError {
125+
guard let responseError = error as? ResponseError else {
126+
return ResponseError.internalError(error.localizedDescription)
127+
}
128+
return responseError
129+
}
108130
}

Sources/SourceKitBazelBSP/Server/MessageHandler/BSPRequestHandler.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
import Foundation
2121
import LanguageServerProtocol
2222

23-
typealias BSPRequestHandler<Request: RequestType> = ((Request, RequestID) throws -> Request.Response)
23+
typealias BSPRequestHandlerCompletion<Request: RequestType> = ((Result<Request.Response, Error>) -> Void)
24+
typealias BSPRequestHandler<Request: RequestType> = (
25+
(Request, RequestID, @escaping BSPRequestHandlerCompletion<Request>) -> Void
26+
)
27+
typealias BSPSyncRequestHandler<Request: RequestType> = ((Request, RequestID) throws -> Request.Response)
2428

2529
/// A type-erased request handler wrapper to allow for dynamic registration of handlers.
2630
final class AnyRequestHandler {

Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ package final class SourceKitBazelBSPServer {
3838
// Everything else will be registered post-init.
3939
let initHandler = InitializeHandler(baseConfig: baseConfig, connection: connection)
4040
let shutdownHandler = ShutdownHandler()
41-
registry.register(requestHandler: { (request: InitializeBuildRequest, id: RequestID) in
41+
registry.register(syncRequestHandler: { (request: InitializeBuildRequest, id: RequestID) in
4242
let result = try initHandler.initializeBuild(request, id)
4343
Self.registerPostInitHandlers(registry: registry, initializedConfig: result.1, connection: connection)
4444
return result.0
4545
})
4646
registry.register(notificationHandler: shutdownHandler.onBuildExit)
47-
registry.register(requestHandler: shutdownHandler.buildShutdown)
47+
registry.register(syncRequestHandler: shutdownHandler.buildShutdown)
4848
return registry
4949
}
5050

@@ -57,7 +57,7 @@ package final class SourceKitBazelBSPServer {
5757
registry.register(notificationHandler: { (_: OnBuildInitializedNotification) in
5858
// no-op
5959
})
60-
registry.register(requestHandler: { (_: WorkspaceWaitForBuildSystemUpdatesRequest, _: RequestID) in
60+
registry.register(syncRequestHandler: { (_: WorkspaceWaitForBuildSystemUpdatesRequest, _: RequestID) in
6161
// FIXME: no-op, no special handling since the code today is not async, but I might be wrong here.
6262
VoidResponse()
6363
})
@@ -66,27 +66,27 @@ package final class SourceKitBazelBSPServer {
6666
// workspace/buildTargets
6767
let targetStore = BazelTargetStoreImpl(initializedConfig: initializedConfig)
6868
let buildTargetsHandler = BuildTargetsHandler(targetStore: targetStore, connection: connection)
69-
registry.register(requestHandler: buildTargetsHandler.workspaceBuildTargets)
69+
registry.register(syncRequestHandler: buildTargetsHandler.workspaceBuildTargets)
7070

7171
// buildTarget/sources
7272
let targetSourcesHandler = TargetSourcesHandler(initializedConfig: initializedConfig, targetStore: targetStore)
73-
registry.register(requestHandler: targetSourcesHandler.buildTargetSources)
73+
registry.register(syncRequestHandler: targetSourcesHandler.buildTargetSources)
7474

7575
// textDocument/sourceKitOptions
7676
let skOptionsHandler = SKOptionsHandler(
7777
initializedConfig: initializedConfig,
7878
targetStore: targetStore,
7979
connection: connection
8080
)
81-
registry.register(requestHandler: skOptionsHandler.textDocumentSourceKitOptions)
81+
registry.register(syncRequestHandler: skOptionsHandler.textDocumentSourceKitOptions)
8282

8383
// buildTarget/prepare
8484
let prepareHandler = PrepareHandler(
8585
initializedConfig: initializedConfig,
8686
targetStore: targetStore,
8787
connection: connection
8888
)
89-
registry.register(requestHandler: prepareHandler.prepareTarget)
89+
registry.register(syncRequestHandler: prepareHandler.prepareTarget)
9090

9191
// OnWatchedFilesDidChangeNotification
9292
let watchedFileChangeHandler = WatchedFileChangeHandler(

Tests/SourceKitBazelBSPTests/BSPMessageHandlerTests.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
// under the License.
1919

2020
import BuildServerProtocol
21+
import Foundation
2122
import LanguageServerProtocol
2223
import LanguageServerProtocolJSONRPC
2324
import Testing
@@ -50,7 +51,7 @@ struct BSPMessageHandlerTests {
5051
let mockResponse = BuildTargetSourcesResponse(items: [])
5152

5253
let handler = BSPMessageHandler()
53-
handler.register(requestHandler: { (_: BuildTargetSourcesRequest, _) in mockResponse })
54+
handler.register(syncRequestHandler: { (_: BuildTargetSourcesRequest, _) in mockResponse })
5455

5556
let request = BuildTargetSourcesRequest(targets: [BuildTargetIdentifier(uri: try URI(string: "file:///test"))],
5657
)
@@ -69,7 +70,7 @@ struct BSPMessageHandlerTests {
6970
func failedRequest() throws {
7071
let mockError = ResponseError.internalError("Test error")
7172
let handler = BSPMessageHandler()
72-
handler.register(requestHandler: { (_: BuildTargetSourcesRequest, _) in throw mockError })
73+
handler.register(syncRequestHandler: { (_: BuildTargetSourcesRequest, _) in throw mockError })
7374

7475
let request = BuildTargetSourcesRequest(targets: [BuildTargetIdentifier(uri: try URI(string: "file:///test"))],
7576
)
@@ -84,6 +85,36 @@ struct BSPMessageHandlerTests {
8485
}
8586
}
8687

88+
@Test
89+
func asyncRequestHandlerCanReplyAsynchronously() throws {
90+
let queue = DispatchQueue(label: "test", qos: .userInteractive)
91+
let semaphore = DispatchSemaphore(value: 0)
92+
let handler = BSPMessageHandler()
93+
handler.register(requestHandler: { (request: BuildTargetSourcesRequest, id, completion) in
94+
queue.asyncAfter(deadline: .now() + 1) {
95+
completion(.success(BuildTargetSourcesResponse(items: [])))
96+
}
97+
})
98+
99+
let request = BuildTargetSourcesRequest(
100+
targets: [BuildTargetIdentifier(uri: try URI(string: "file:///test"))]
101+
)
102+
103+
var receivedResponse: LSPResult<BuildTargetSourcesResponse>?
104+
handler.handle(request, id: RequestID.number(1)) { result in
105+
receivedResponse = result
106+
semaphore.signal()
107+
}
108+
109+
semaphore.wait()
110+
111+
let result = try #require(receivedResponse)
112+
switch result {
113+
case .success(let response): #expect(response == BuildTargetSourcesResponse(items: []))
114+
case .failure(let error): Issue.record("Expected success but got error: \(error)")
115+
}
116+
}
117+
87118
@Test
88119
func unknownNotification() throws {
89120
var initialized = false

0 commit comments

Comments
 (0)