Skip to content

Commit d70a68f

Browse files
committed
Wait until initialization has finished before starting a work done progress
1 parent c399b43 commit d70a68f

File tree

2 files changed

+62
-13
lines changed

2 files changed

+62
-13
lines changed

Sources/SourceKitLSP/SourceKitLSPServer.swift

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

118+
/// A queue so we can have synchronous `startProgress` and `endProgress` functions that don't need to wait for the
119+
/// work done progress to be started or ended.
120+
private let queue = AsyncQueue<Serial>()
121+
118122
/// How many active tasks are running.
119123
///
120124
/// A work done progress should be displayed if activeTasks > 0
@@ -135,13 +139,16 @@ final actor WorkDoneProgressState {
135139
/// Start a new task, creating a new `WorkDoneProgress` if none is running right now.
136140
///
137141
/// - Parameter server: The server that is used to create the `WorkDoneProgress` on the client
138-
func startProgress(server: SourceKitLSPServer) async {
142+
nonisolated func startProgress(server: SourceKitLSPServer) {
143+
queue.async {
144+
await self.startProgressImpl(server: server)
145+
}
146+
}
147+
148+
func startProgressImpl(server: SourceKitLSPServer) async {
149+
await server.waitUntilInitialized()
139150
activeTasks += 1
140151
guard await server.capabilityRegistry?.clientCapabilities.window?.workDoneProgress ?? false else {
141-
// If the client doesn't support workDoneProgress, keep track of the active task count but don't update the state.
142-
// That way, if we call `startProgress` before initialization finishes, we won't send the
143-
// `CreateWorkDoneProgressRequest` but if we call `startProgress` again after initialization finished (and thus
144-
// the capability is set), we will create the work done progress.
145152
return
146153
}
147154
if state == .noProgress {
@@ -180,7 +187,13 @@ final actor WorkDoneProgressState {
180187
/// If this drops the active task count to 0, the work done progress is ended on the client.
181188
///
182189
/// - Parameter server: The server that is used to send and update of the `WorkDoneProgress` to the client
183-
func endProgress(server: SourceKitLSPServer) async {
190+
nonisolated func endProgress(server: SourceKitLSPServer) {
191+
queue.async {
192+
await self.endProgressImpl(server: server)
193+
}
194+
}
195+
196+
func endProgressImpl(server: SourceKitLSPServer) async {
184197
assert(activeTasks > 0, "Unbalanced startProgress/endProgress calls")
185198
activeTasks -= 1
186199
guard await server.capabilityRegistry?.clientCapabilities.window?.workDoneProgress ?? false else {
@@ -440,11 +453,16 @@ public actor SourceKitLSPServer {
440453
/// The connection to the editor.
441454
public let client: Connection
442455

456+
/// Set to `true` after the `SourceKitLSPServer` has send the reply to the `InitializeRequest`.
457+
///
458+
/// Initialization can be awaited using `waitUntilInitialized`.
459+
private var initialized: Bool = false
460+
443461
var options: Options
444462

445463
let toolchainRegistry: ToolchainRegistry
446464

447-
var capabilityRegistry: CapabilityRegistry?
465+
public var capabilityRegistry: CapabilityRegistry?
448466

449467
var languageServices: [LanguageServerType: [LanguageService]] = [:]
450468

@@ -508,19 +526,15 @@ public actor SourceKitLSPServer {
508526
self.inProgressRequests[id] = task
509527
}
510528

511-
let fs: FileSystem
512-
513529
var onExit: () -> Void
514530

515531
/// Creates a language server for the given client.
516532
public init(
517533
client: Connection,
518-
fileSystem: FileSystem = localFileSystem,
519534
toolchainRegistry: ToolchainRegistry,
520535
options: Options,
521536
onExit: @escaping () -> Void = {}
522537
) {
523-
self.fs = fileSystem
524538
self.toolchainRegistry = toolchainRegistry
525539
self.options = options
526540
self.onExit = onExit
@@ -534,6 +548,22 @@ public actor SourceKitLSPServer {
534548
])
535549
}
536550

551+
/// Await until the server has send the reply to the initialize request.
552+
func waitUntilInitialized() async {
553+
// The polling of `initialized` is not perfect but it should be OK, because
554+
// - In almost all cases the server should already be initialized.
555+
// - If it's not initialized, we expect initialization to finish fairly quickly. Even if initialization takes 5s
556+
// this only results in 50 polls, which is acceptable.
557+
// Alternative solutions that signal via an async sequence seem overkill here.
558+
while !initialized {
559+
do {
560+
try await Task.sleep(for: .seconds(0.1))
561+
} catch {
562+
break
563+
}
564+
}
565+
}
566+
537567
/// Search through all the parent directories of `uri` and check if any of these directories contain a workspace
538568
/// capable of handling `uri`.
539569
///
@@ -958,6 +988,8 @@ extension SourceKitLSPServer: MessageHandler {
958988
switch request {
959989
case let request as RequestAndReply<InitializeRequest>:
960990
await request.reply { try await initialize(request.params) }
991+
// Only set `initialized` to `true` after we have sent the response to the initialize request to the client.
992+
initialized = true
961993
case let request as RequestAndReply<ShutdownRequest>:
962994
await request.reply { try await shutdown(request.params) }
963995
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)