Skip to content

Commit 79514e6

Browse files
committed
Wait until initialization has finish before starting a work done progress
1 parent 4d946c2 commit 79514e6

File tree

2 files changed

+62
-9
lines changed

2 files changed

+62
-9
lines changed

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ final actor WorkDoneProgressState {
114114
case progressCreationFailed
115115
}
116116

117+
/// A queue so we can have synchronous `startProgress` and `endProgress` functions that don't need to wait for the
118+
/// work done progress to be started or ended.
119+
private let queue = AsyncQueue<Serial>()
120+
117121
/// How many active tasks are running.
118122
///
119123
/// A work done progress should be displayed if activeTasks > 0
@@ -134,7 +138,14 @@ final actor WorkDoneProgressState {
134138
/// Start a new task, creating a new `WorkDoneProgress` if none is running right now.
135139
///
136140
/// - Parameter server: The server that is used to create the `WorkDoneProgress` on the client
137-
func startProgress(server: SourceKitLSPServer) async {
141+
nonisolated func startProgress(server: SourceKitLSPServer) {
142+
queue.async {
143+
self.startProgress(server: server)
144+
}
145+
}
146+
147+
func startProgressImpl(server: SourceKitLSPServer) async {
148+
await server.waitUntilInitialized()
138149
activeTasks += 1
139150
guard await server.capabilityRegistry?.clientCapabilities.window?.workDoneProgress ?? false else {
140151
// If the client doesn't support workDoneProgress, keep track of the active task count but don't update the state.
@@ -179,7 +190,13 @@ final actor WorkDoneProgressState {
179190
/// If this drops the active task count to 0, the work done progress is ended on the client.
180191
///
181192
/// - Parameter server: The server that is used to send and update of the `WorkDoneProgress` to the client
182-
func endProgress(server: SourceKitLSPServer) async {
193+
nonisolated func endProgress(server: SourceKitLSPServer) {
194+
queue.async {
195+
await self.endProgressImpl(server: server)
196+
}
197+
}
198+
199+
func endProgressImpl(server: SourceKitLSPServer) async {
183200
assert(activeTasks > 0, "Unbalanced startProgress/endProgress calls")
184201
activeTasks -= 1
185202
guard await server.capabilityRegistry?.clientCapabilities.window?.workDoneProgress ?? false else {
@@ -439,11 +456,16 @@ public actor SourceKitLSPServer {
439456
/// The connection to the editor.
440457
public let client: Connection
441458

459+
/// Set to `true` after the `SourceKitLSPServer` has send the reply to the `InitializeRequest`.
460+
///
461+
/// Initialization can be awaited using `waitUntilInitialized`.
462+
private var initialized: Bool = false
463+
442464
var options: Options
443465

444466
let toolchainRegistry: ToolchainRegistry
445467

446-
var capabilityRegistry: CapabilityRegistry?
468+
public var capabilityRegistry: CapabilityRegistry?
447469

448470
var languageServices: [LanguageServerType: [LanguageService]] = [:]
449471

@@ -507,19 +529,15 @@ public actor SourceKitLSPServer {
507529
self.inProgressRequests[id] = task
508530
}
509531

510-
let fs: FileSystem
511-
512532
var onExit: () -> Void
513533

514534
/// Creates a language server for the given client.
515535
public init(
516536
client: Connection,
517-
fileSystem: FileSystem = localFileSystem,
518537
toolchainRegistry: ToolchainRegistry,
519538
options: Options,
520539
onExit: @escaping () -> Void = {}
521540
) {
522-
self.fs = fileSystem
523541
self.toolchainRegistry = toolchainRegistry
524542
self.options = options
525543
self.onExit = onExit
@@ -533,6 +551,22 @@ public actor SourceKitLSPServer {
533551
])
534552
}
535553

554+
/// Await until the server has send the reply to the initialize request.
555+
func waitUntilInitialized() async {
556+
// The polling of `initialized` is not perfect but it should be OK, because
557+
// - In almost all cases the server should already be initialized.
558+
// - If it's not initialized, we expect initialization to finish fairly quickly. Even if initialization takes 5s
559+
// this only results in 50 polls, which is acceptable.
560+
// Alternative solutions that signal via an async sequence seem overkill here.
561+
while !initialized {
562+
do {
563+
try await Task.sleep(for: .seconds(0.1))
564+
} catch {
565+
break
566+
}
567+
}
568+
}
569+
536570
/// Search through all the parent directories of `uri` and check if any of these directories contain a workspace
537571
/// capable of handling `uri`.
538572
///
@@ -952,6 +986,8 @@ extension SourceKitLSPServer: MessageHandler {
952986
switch request {
953987
case let request as RequestAndReply<InitializeRequest>:
954988
await request.reply { try await initialize(request.params) }
989+
// Only set `initialized` to `true` after we have sent the response to the initialize request to the client.
990+
initialized = true
955991
case let request as RequestAndReply<ShutdownRequest>:
956992
await request.reply { try await shutdown(request.params) }
957993
case let request as RequestAndReply<WorkspaceSymbolsRequest>:

Sources/SourceKitLSP/WorkDoneProgressManager.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,37 @@ final class WorkDoneProgressManager {
2323
private let queue = AsyncQueue<Serial>()
2424
private let server: SourceKitLSPServer
2525

26-
init?(server: SourceKitLSPServer, capabilityRegistry: CapabilityRegistry, title: String, message: String? = nil) {
26+
convenience init?(server: SourceKitLSPServer, title: String, message: String? = nil, percentage: Int? = nil) async {
27+
guard let capabilityRegistry = await server.capabilityRegistry else {
28+
return nil
29+
}
30+
self.init(server: server, capabilityRegistry: capabilityRegistry, title: title, message: message)
31+
}
32+
33+
init?(
34+
server: SourceKitLSPServer,
35+
capabilityRegistry: CapabilityRegistry,
36+
title: String,
37+
message: String? = nil,
38+
percentage: Int? = nil
39+
) {
2740
guard capabilityRegistry.clientCapabilities.window?.workDoneProgress ?? false else {
2841
return nil
2942
}
3043
self.token = .string("WorkDoneProgress-\(UUID())")
3144
self.server = server
3245
queue.async { [server, token] in
46+
await server.waitUntilInitialized()
3347
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
3448
_ = server.client.send(CreateWorkDoneProgressRequest(token: token)) { result in
3549
continuation.resume()
3650
}
3751
}
3852
await server.sendNotificationToClient(
39-
WorkDoneProgress(token: token, value: .begin(WorkDoneProgressBegin(title: title, message: message)))
53+
WorkDoneProgress(
54+
token: token,
55+
value: .begin(WorkDoneProgressBegin(title: title, message: message, percentage: percentage))
56+
)
4057
)
4158
}
4259
}

0 commit comments

Comments
 (0)