Skip to content

Commit c8447fb

Browse files
committed
Wait until initialization has finish before starting a work done progress
x
1 parent bf0407c commit c8447fb

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
@@ -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,13 +138,16 @@ 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+
await self.startProgressImpl(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 {
140-
// If the client doesn't support workDoneProgress, keep track of the active task count but don't update the state.
141-
// That way, if we call `startProgress` before initialization finishes, we won't send the
142-
// `CreateWorkDoneProgressRequest` but if we call `startProgress` again after initialization finished (and thus
143-
// the capability is set), we will create the work done progress.
144151
return
145152
}
146153
if state == .noProgress {
@@ -179,7 +186,13 @@ final actor WorkDoneProgressState {
179186
/// If this drops the active task count to 0, the work done progress is ended on the client.
180187
///
181188
/// - Parameter server: The server that is used to send and update of the `WorkDoneProgress` to the client
182-
func endProgress(server: SourceKitLSPServer) async {
189+
nonisolated func endProgress(server: SourceKitLSPServer) {
190+
queue.async {
191+
await self.endProgressImpl(server: server)
192+
}
193+
}
194+
195+
func endProgressImpl(server: SourceKitLSPServer) async {
183196
assert(activeTasks > 0, "Unbalanced startProgress/endProgress calls")
184197
activeTasks -= 1
185198
guard await server.capabilityRegistry?.clientCapabilities.window?.workDoneProgress ?? false else {
@@ -439,11 +452,16 @@ public actor SourceKitLSPServer {
439452
/// The connection to the editor.
440453
public let client: Connection
441454

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

444462
let toolchainRegistry: ToolchainRegistry
445463

446-
var capabilityRegistry: CapabilityRegistry?
464+
public var capabilityRegistry: CapabilityRegistry?
447465

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

@@ -507,19 +525,15 @@ public actor SourceKitLSPServer {
507525
self.inProgressRequests[id] = task
508526
}
509527

510-
let fs: FileSystem
511-
512528
var onExit: () -> Void
513529

514530
/// Creates a language server for the given client.
515531
public init(
516532
client: Connection,
517-
fileSystem: FileSystem = localFileSystem,
518533
toolchainRegistry: ToolchainRegistry,
519534
options: Options,
520535
onExit: @escaping () -> Void = {}
521536
) {
522-
self.fs = fileSystem
523537
self.toolchainRegistry = toolchainRegistry
524538
self.options = options
525539
self.onExit = onExit
@@ -533,6 +547,22 @@ public actor SourceKitLSPServer {
533547
])
534548
}
535549

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