Skip to content

Commit 331f156

Browse files
authored
Merge pull request #2311 from ahoppen/long-build-server-init
Do not block SourceKit-LSP functionality when a build server takes long to initialize
2 parents b423010 + a6c291b commit 331f156

File tree

14 files changed

+322
-228
lines changed

14 files changed

+322
-228
lines changed

Sources/BuildServerIntegration/BuildServerManager.swift

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,26 @@ package actor BuildServerManager: QueueBasedMessageHandler {
343343
}
344344

345345
/// Provider of file to main file mappings.
346-
private var mainFilesProvider: MainFilesProvider?
346+
///
347+
/// Force-unwrapped optional because initializing it requires access to `self`.
348+
private var mainFilesProvider: Task<MainFilesProvider?, Never>! {
349+
didSet {
350+
// Must only be set once
351+
precondition(oldValue == nil)
352+
precondition(mainFilesProvider != nil)
353+
}
354+
}
355+
356+
package func mainFilesProvider<T: MainFilesProvider>(as: T.Type) async -> T? {
357+
guard let mainFilesProvider = mainFilesProvider else {
358+
return nil
359+
}
360+
guard let index = await mainFilesProvider.value as? T else {
361+
logger.fault("Expected the main files provider of the build server manager to be an `\(T.self)`")
362+
return nil
363+
}
364+
return index
365+
}
347366

348367
/// Build server delegate that will receive notifications about setting changes, etc.
349368
private weak var delegate: BuildServerManagerDelegate?
@@ -465,7 +484,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
465484
toolchainRegistry: ToolchainRegistry,
466485
options: SourceKitLSPOptions,
467486
connectionToClient: BuildServerManagerConnectionToClient,
468-
buildServerHooks: BuildServerHooks
487+
buildServerHooks: BuildServerHooks,
488+
createMainFilesProvider:
489+
@escaping @Sendable (
490+
SourceKitInitializeBuildResponseData?, _ mainFilesChangedCallback: @escaping @Sendable () async -> Void
491+
) async -> MainFilesProvider?
469492
) async {
470493
self.toolchainRegistry = toolchainRegistry
471494
self.options = options
@@ -578,6 +601,11 @@ package actor BuildServerManager: QueueBasedMessageHandler {
578601
await buildServerAdapter.send(OnBuildInitializedNotification())
579602
return initializeResponse
580603
}
604+
self.mainFilesProvider = Task {
605+
await createMainFilesProvider(initializationData) { [weak self] in
606+
await self?.mainFilesChanged()
607+
}
608+
}
581609
}
582610

583611
/// Explicitly shut down the build server.
@@ -630,12 +658,6 @@ package actor BuildServerManager: QueueBasedMessageHandler {
630658
self.delegate = delegate
631659
}
632660

633-
/// - Note: Needed because we need the `indexStorePath` and `indexDatabasePath` from the build server to create an
634-
/// IndexStoreDB, which serves as the `MainFilesProvider`. And thus this can't be set during initialization.
635-
package func setMainFilesProvider(_ mainFilesProvider: MainFilesProvider?) {
636-
self.mainFilesProvider = mainFilesProvider
637-
}
638-
639661
// MARK: Handling messages from the build server
640662

641663
package func handle(notification: some NotificationType) async {
@@ -1274,13 +1296,12 @@ package actor BuildServerManager: QueueBasedMessageHandler {
12741296
}
12751297

12761298
private func buildTargets() async throws -> [BuildTargetIdentifier: BuildTargetInfo] {
1277-
guard let buildServerAdapter = try await buildServerAdapterAfterInitialized else {
1278-
return [:]
1279-
}
1280-
12811299
let request = WorkspaceBuildTargetsRequest()
12821300
let result = try await cachedBuildTargets.get(request, isolation: self) { request in
12831301
let result = try await withTimeout(self.options.buildServerWorkspaceRequestsTimeoutOrDefault) {
1302+
guard let buildServerAdapter = try await self.buildServerAdapterAfterInitialized else {
1303+
return [:]
1304+
}
12841305
let buildTargets = try await buildServerAdapter.send(request).targets
12851306
let (depths, dependents) = await self.targetDepthsAndDependents(for: buildTargets)
12861307
var result: [BuildTargetIdentifier: BuildTargetInfo] = [:]
@@ -1325,7 +1346,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
13251346
}
13261347

13271348
package func sourceFiles(in targets: Set<BuildTargetIdentifier>) async throws -> [SourcesItem] {
1328-
guard let buildServerAdapter = try await buildServerAdapterAfterInitialized, !targets.isEmpty else {
1349+
guard !targets.isEmpty else {
13291350
return []
13301351
}
13311352

@@ -1346,6 +1367,9 @@ package actor BuildServerManager: QueueBasedMessageHandler {
13461367

13471368
let response = try await cachedTargetSources.get(request, isolation: self) { request in
13481369
try await withTimeout(self.options.buildServerWorkspaceRequestsTimeoutOrDefault) {
1370+
guard let buildServerAdapter = try await self.buildServerAdapterAfterInitialized else {
1371+
return BuildTargetSourcesResponse(items: [])
1372+
}
13491373
return try await buildServerAdapter.send(request)
13501374
} resultReceivedAfterTimeout: { newResult in
13511375
await self.buildTargetsDidChange(.sourceFilesReceivedResultAfterTimeout(request: request, newResult: newResult))
@@ -1393,7 +1417,6 @@ package actor BuildServerManager: QueueBasedMessageHandler {
13931417
/// - Important: This method returns both buildable and non-buildable source files. Callers need to check
13941418
/// `SourceFileInfo.isBuildable` if they are only interested in buildable source files.
13951419
private func sourceFilesAndDirectories() async throws -> SourceFilesAndDirectories {
1396-
let supportsOutputPaths = await initializationData?.outputPathsProvider ?? false
13971420

13981421
return try await cachedSourceFilesAndDirectories.get(
13991422
SourceFilesAndDirectoriesKey(),
@@ -1411,7 +1434,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
14111434
for sourceItem in sourcesItem.sources {
14121435
let sourceKitData = sourceItem.sourceKitData
14131436
let outputPath: OutputPath? =
1414-
if !supportsOutputPaths {
1437+
if !(await self.initializationData?.outputPathsProvider ?? false) {
14151438
.notSupported
14161439
} else if let outputPath = sourceKitData?.outputPath {
14171440
.path(outputPath)
@@ -1499,7 +1522,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
14991522
/// path is `/tmp`). If the realpath that indexstore-db returns could not be found in the build server's source files
15001523
/// but the standardized path is part of the source files, return the standardized path instead.
15011524
package func mainFiles(containing uri: DocumentURI) async -> [DocumentURI] {
1502-
guard let mainFilesProvider else {
1525+
guard let mainFilesProvider = await mainFilesProvider.value else {
15031526
return [uri]
15041527
}
15051528
let mainFiles = Array(await mainFilesProvider.mainFiles(containing: uri, crossLanguage: false))

Sources/DocumentationLanguageService/DoccDocumentationHandler.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ extension DocumentationLanguageService {
101101
}
102102
guard let moduleName, symbolName == moduleName else {
103103
// This is a symbol extension page. Find the symbol so that we can include it in the request.
104-
guard let index = workspace.index(checkedFor: .deletedFiles) else {
104+
guard let index = await workspace.index(checkedFor: .deletedFiles) else {
105105
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
106106
}
107107
return try await sourceKitLSPServer.withOnDiskDocumentManager { onDiskDocumentManager in
@@ -206,7 +206,7 @@ extension DocumentationLanguageService {
206206
return nil
207207
}
208208
let catalogIndex = try await documentationManager.catalogIndex(for: catalogURL)
209-
guard let index = workspace.index(checkedFor: .deletedFiles) else {
209+
guard let index = await workspace.index(checkedFor: .deletedFiles) else {
210210
return nil
211211
}
212212
let symbolInformation = try await index.doccSymbolInformation(

Sources/SourceKitLSP/IndexProgressManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ actor IndexProgressManager {
8383
return
8484
}
8585
var status = IndexProgressStatus.upToDate
86-
for indexManager in await sourceKitLSPServer.workspaces.compactMap({ $0.semanticIndexManager }) {
86+
for indexManager in await sourceKitLSPServer.workspaces.asyncCompactMap({ await $0.semanticIndexManager }) {
8787
status = status.merging(with: await indexManager.progressStatus)
8888
}
8989

Sources/SourceKitLSP/Rename.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ extension SourceKitLSPServer {
290290
// location. This way we are still able to rename occurrences in files where eg. only one line has been modified but
291291
// all the line:column locations of occurrences are still up-to-date.
292292
// This should match the check level in prepareRename.
293-
guard let usr = renameResult.usr, let index = workspace.index(checkedFor: .deletedFiles) else {
293+
guard let usr = renameResult.usr, let index = await workspace.index(checkedFor: .deletedFiles) else {
294294
// We don't have enough information to perform a cross-file rename.
295295
return renameResult.edits
296296
}
@@ -442,7 +442,7 @@ extension SourceKitLSPServer {
442442
var prepareRenameResult = languageServicePrepareRename.prepareRename
443443

444444
guard
445-
let index = workspace.index(checkedFor: .deletedFiles),
445+
let index = await workspace.index(checkedFor: .deletedFiles),
446446
let usr = languageServicePrepareRename.usr,
447447
let oldName = try await self.getCrossLanguageName(forUsr: usr, workspace: workspace, index: index),
448448
var definitionName = oldName.definitionName

Sources/SourceKitLSP/SourceKitIndexDelegate.swift

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ import SwiftExtensions
1919
/// `IndexDelegate` for the SourceKit workspace.
2020
actor SourceKitIndexDelegate: IndexDelegate {
2121
/// Registered `MainFilesDelegate`s to notify when main files change.
22-
var mainFilesChangedCallbacks: [@Sendable () async -> Void] = []
22+
var mainFilesChangedCallback: @Sendable () async -> Void
2323

2424
/// The count of pending unit events. Whenever this transitions to 0, it represents a time where
2525
/// the index finished processing known events. Of course, that may have already changed by the
2626
/// time we are notified.
2727
let pendingUnitCount = AtomicInt32(initialValue: 0)
2828

29-
package init() {}
29+
package init(mainFilesChangedCallback: @escaping @Sendable () async -> Void) {
30+
self.mainFilesChangedCallback = mainFilesChangedCallback
31+
}
3032

3133
nonisolated package func processingAddedPending(_ count: Int) {
3234
pendingUnitCount.value += Int32(count)
@@ -53,13 +55,6 @@ actor SourceKitIndexDelegate: IndexDelegate {
5355

5456
private func indexChanged() async {
5557
logger.debug("IndexStoreDB changed")
56-
for callback in mainFilesChangedCallbacks {
57-
await callback()
58-
}
59-
}
60-
61-
/// Register a delegate to receive notifications when main files change.
62-
package func addMainFileChangedCallback(_ callback: @escaping @Sendable () async -> Void) {
63-
mainFilesChangedCallbacks.append(callback)
58+
await mainFilesChangedCallback()
6459
}
6560
}

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,8 +1257,6 @@ extension SourceKitLSPServer {
12571257
taskGroup.addTask {
12581258
await orLog("Shutting down build server") {
12591259
await workspace.buildServerManager.shutdown()
1260-
await workspace.buildServerManager.setMainFilesProvider(nil)
1261-
workspace.closeIndex()
12621260
}
12631261
}
12641262
}
@@ -1306,7 +1304,7 @@ extension SourceKitLSPServer {
13061304
}
13071305
let isIndexing =
13081306
await workspaces
1309-
.compactMap(\.semanticIndexManager)
1307+
.asyncCompactMap { await $0.semanticIndexManager }
13101308
.asyncContains { await $0.progressStatus != .upToDate }
13111309
return IsIndexingResponse(indexing: isIndexing)
13121310
}
@@ -1568,7 +1566,7 @@ extension SourceKitLSPServer {
15681566
target = nil
15691567
}
15701568
let didPrepareTarget: Bool?
1571-
if request.prepareTarget, let target, let semanticIndexManager = workspace.semanticIndexManager {
1569+
if request.prepareTarget, let target, let semanticIndexManager = await workspace.semanticIndexManager {
15721570
didPrepareTarget = await semanticIndexManager.prepareTargetsForSourceKitOptions(target: target)
15731571
} else {
15741572
didPrepareTarget = nil
@@ -1669,7 +1667,7 @@ extension SourceKitLSPServer {
16691667
}
16701668
var symbolsAndIndex: [(symbol: SymbolOccurrence, index: CheckedIndex)] = []
16711669
for workspace in workspaces {
1672-
guard let index = workspace.index(checkedFor: .deletedFiles) else {
1670+
guard let index = await workspace.index(checkedFor: .deletedFiles) else {
16731671
continue
16741672
}
16751673
var symbolOccurrences: [SymbolOccurrence] = []
@@ -2084,7 +2082,7 @@ extension SourceKitLSPServer {
20842082
// overrides.
20852083
if let location = locations.only,
20862084
let usr = symbol.usr,
2087-
let index = workspace.index(checkedFor: .deletedFiles),
2085+
let index = await workspace.index(checkedFor: .deletedFiles),
20882086
await isAtCanonicalOriginatorLocation(location)
20892087
{
20902088
let baseUSRs = index.occurrences(ofUSR: usr, roles: .overrideOf).flatMap {

Sources/SourceKitLSP/TestDiscovery.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ extension SourceKitLSPServer {
207207
// - For all files that don't have any in-memory modifications, include swift-testing tests from the syntactic test
208208
// index.
209209
// - All files that have in-memory modifications are syntactically scanned for tests here.
210-
let index = workspace.index(checkedFor: .inMemoryModifiedFiles(documentManager))
210+
let index = await workspace.index(checkedFor: .inMemoryModifiedFiles(documentManager))
211211

212212
// TODO: Remove this workaround once https://github.com/swiftlang/swift/issues/75600 is fixed
213213
func documentManagerHasInMemoryModifications(_ uri: DocumentURI) -> Bool {
@@ -252,7 +252,7 @@ extension SourceKitLSPServer {
252252
)
253253
let filesWithTestsFromSemanticIndex = Set(testsFromSemanticIndex.map(\.testItem.location.uri))
254254

255-
let indexOnlyDiscardingDeletedFiles = workspace.index(checkedFor: .deletedFiles)
255+
let indexOnlyDiscardingDeletedFiles = await workspace.index(checkedFor: .deletedFiles)
256256

257257
let syntacticTestsToInclude =
258258
testsFromSyntacticIndex
@@ -334,7 +334,7 @@ extension SourceKitLSPServer {
334334
let indexCheckLevel: IndexCheckLevel =
335335
syntacticTests == nil ? .deletedFiles : .inMemoryModifiedFiles(documentManager)
336336

337-
if let index = workspace.index(checkedFor: indexCheckLevel) {
337+
if let index = await workspace.index(checkedFor: indexCheckLevel) {
338338
var syntacticSwiftTestingTests: [AnnotatedTestItem] {
339339
syntacticTests?.filter { $0.testItem.style == TestStyle.swiftTesting } ?? []
340340
}

0 commit comments

Comments
 (0)