Skip to content

Commit b36580f

Browse files
committed
Don't wait for build graph generation when file is changed
Since we re-index source files if the build server sends us a `buildTarget/didChange` notification, we no longer need to wait for an up-to-date build graph when a file is modified. This resolves an issue that causes a `Scheduling tasks` progress to appear in the status bar whenever a file in the project is changed. Before #1973, this happened fairly frequently during the initial indexing when a header file was written into the build directory. After that PR the `Scheduling Indexing` status appears a lot more frequently, namely every time the index’s database is modified, which happens quite all the time during indexing...
1 parent 71dfc73 commit b36580f

File tree

2 files changed

+70
-50
lines changed

2 files changed

+70
-50
lines changed

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 70 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,11 @@ package final actor SemanticIndexManager {
175175

176176
/// The tasks to generate the build graph (resolving package dependencies, generating the build description,
177177
/// ...) and to schedule indexing of modified tasks.
178-
private var scheduleIndexingTasks: [UUID: Task<Void, Never>] = [:]
178+
private var buildGraphGenerationTasks: [UUID: Task<Void, Never>] = [:]
179+
180+
/// Tasks that were created from `workspace/didChangeWatchedFiles` or `buildTarget/didChange` notifications. Needed
181+
/// so that we can wait for these tasks to be handled in the poll index request.
182+
private var fileOrBuildTargetChangedTasks: [UUID: Task<Void, Never>] = [:]
179183

180184
private let preparationUpToDateTracker = UpToDateTracker<BuildTargetIdentifier>()
181185

@@ -225,7 +229,7 @@ package final actor SemanticIndexManager {
225229
if inProgressPreparationTasks.values.contains(where: { $0.purpose == .forEditorFunctionality }) {
226230
return .preparingFileForEditorFunctionality
227231
}
228-
if !scheduleIndexingTasks.isEmpty {
232+
if !buildGraphGenerationTasks.isEmpty {
229233
return .schedulingIndexing
230234
}
231235
let preparationTasks = inProgressPreparationTasks.mapValues { inProgressTask in
@@ -270,46 +274,37 @@ package final actor SemanticIndexManager {
270274
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
271275
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
272276
///
273-
/// If `filesToIndex` is `nil`, all files in the build system with out-of-date units are indexed.
274-
///
275-
/// If `ensureAllUnitsRegisteredInIndex` is `true`, ensure that all units are registered in the index before
276-
/// triggering the indexing. This is a costly operation since it iterates through all the unit files on the file
277+
/// This is a costly operation since it iterates through all the unit files on the file
277278
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
278279
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
279280
/// build the indexstore-db) and `false` for all subsequent indexing.
280-
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
281-
filesToIndex: [DocumentURI]?,
282-
ensureAllUnitsRegisteredInIndex: Bool,
283-
indexFilesWithUpToDateUnit: Bool
284-
) async {
281+
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: Bool) async {
285282
let taskId = UUID()
286283
let generateBuildGraphTask = Task(priority: .low) {
287284
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "build-graph-generation") {
288285
await hooks.buildGraphGenerationDidStart?()
289286
await self.buildSystemManager.waitForUpToDateBuildGraph()
290-
if ensureAllUnitsRegisteredInIndex {
291-
index.pollForUnitChangesAndWait()
292-
}
287+
// Polling for unit changes is a costly operation since it iterates through all the unit files on the file
288+
// system but if existing unit files are not known to the index, we might re-index those files even if they are
289+
// up-to-date. This operation is worth the cost during initial indexing and during the manual re-index command.
290+
index.pollForUnitChangesAndWait()
293291
await hooks.buildGraphGenerationDidFinish?()
294292
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
295293
// https://github.com/swiftlang/swift/issues/75602
296294
let filesToIndex: [DocumentURI] =
297-
if let filesToIndex {
298-
filesToIndex
299-
} else {
300-
await orLog("Getting files to index") {
301-
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
302-
} ?? []
303-
}
304-
_ = await self.scheduleIndexing(
295+
await orLog("Getting files to index") {
296+
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
297+
} ?? []
298+
await self.scheduleIndexing(
305299
of: filesToIndex,
300+
waitForBuildGraphGenerationTasks: false,
306301
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
307302
priority: .low
308303
)
309-
scheduleIndexingTasks[taskId] = nil
304+
buildGraphGenerationTasks[taskId] = nil
310305
}
311306
}
312-
scheduleIndexingTasks[taskId] = generateBuildGraphTask
307+
buildGraphGenerationTasks[taskId] = generateBuildGraphTask
313308
indexProgressStatusDidChange()
314309
}
315310

@@ -318,15 +313,11 @@ package final actor SemanticIndexManager {
318313
package func scheduleReindex() async {
319314
await indexStoreUpToDateTracker.markAllKnownOutOfDate()
320315
await preparationUpToDateTracker.markAllKnownOutOfDate()
321-
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
322-
filesToIndex: nil,
323-
ensureAllUnitsRegisteredInIndex: false,
324-
indexFilesWithUpToDateUnit: true
325-
)
316+
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: true)
326317
}
327318

328319
private func waitForBuildGraphGenerationTasks() async {
329-
await scheduleIndexingTasks.values.concurrentForEach { await $0.value }
320+
await buildGraphGenerationTasks.values.concurrentForEach { await $0.value }
330321
}
331322

332323
/// Wait for all in-progress index tasks to finish.
@@ -336,6 +327,8 @@ package final actor SemanticIndexManager {
336327
// can await the index tasks below.
337328
await waitForBuildGraphGenerationTasks()
338329

330+
await fileOrBuildTargetChangedTasks.concurrentForEach { await $0.value.value }
331+
339332
await inProgressIndexTasks.concurrentForEach { (_, inProgress) in
340333
switch inProgress.state {
341334
case .creatingIndexTask:
@@ -358,14 +351,16 @@ package final actor SemanticIndexManager {
358351
logger.info(
359352
"Waiting for up-to-date index for \(uris.map { $0.fileURL?.lastPathComponent ?? $0.stringValue }.joined(separator: ", "))"
360353
)
361-
// If there's a build graph update in progress wait for that to finish so we can discover new files in the build
362-
// system.
363-
await waitForBuildGraphGenerationTasks()
364354

365355
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
366356
// - Wait for the existing index operations to finish if they have the same number of files.
367357
// - Reschedule the background index task in favor of an index task with fewer source files.
368-
await self.scheduleIndexing(of: uris, indexFilesWithUpToDateUnit: false, priority: nil).value
358+
await self.scheduleIndexing(
359+
of: uris,
360+
waitForBuildGraphGenerationTasks: true,
361+
indexFilesWithUpToDateUnit: false,
362+
priority: nil
363+
).value
369364
index.pollForUnitChangesAndWait()
370365
logger.debug("Done waiting for up-to-date index")
371366
}
@@ -399,11 +394,19 @@ package final actor SemanticIndexManager {
399394
await preparationUpToDateTracker.markOutOfDate(outOfDateTargets)
400395
}
401396

402-
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
403-
filesToIndex: changedFiles,
404-
ensureAllUnitsRegisteredInIndex: false,
405-
indexFilesWithUpToDateUnit: false
406-
)
397+
// We need to invalidate the preparation status of the changed files immediately so that we re-prepare its target
398+
// eg. on a `workspace/sourceKitOptions` request. But the actual indexing can happen using a background task.
399+
// We don't need a queue here because we don't care about the order in which we schedule re-indexing of files.
400+
let uuid = UUID()
401+
fileOrBuildTargetChangedTasks[uuid] = Task {
402+
defer { fileOrBuildTargetChangedTasks[uuid] = nil }
403+
await scheduleIndexing(
404+
of: changedFiles,
405+
waitForBuildGraphGenerationTasks: true,
406+
indexFilesWithUpToDateUnit: false,
407+
priority: .low
408+
)
409+
}
407410
}
408411

409412
package func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async {
@@ -425,16 +428,24 @@ package final actor SemanticIndexManager {
425428
await preparationUpToDateTracker.markAllKnownOutOfDate()
426429
}
427430

428-
await orLog("Scheduling re-indexing of changed targets") {
429-
var sourceFiles = try await self.buildSystemManager.sourceFiles(includeNonBuildableFiles: false)
430-
if let targets {
431-
sourceFiles = sourceFiles.filter { !$0.value.targets.isDisjoint(with: targets) }
431+
// We need to invalidate the preparation status of the changed files immediately so that we re-prepare its target
432+
// eg. on a `workspace/sourceKitOptions` request. But the actual indexing can happen using a background task.
433+
// We don't need a queue here because we don't care about the order in which we schedule re-indexing of files.
434+
let uuid = UUID()
435+
fileOrBuildTargetChangedTasks[uuid] = Task {
436+
defer { fileOrBuildTargetChangedTasks[uuid] = nil }
437+
await orLog("Scheduling re-indexing of changed targets") {
438+
var sourceFiles = try await self.buildSystemManager.sourceFiles(includeNonBuildableFiles: false)
439+
if let targets {
440+
sourceFiles = sourceFiles.filter { !$0.value.targets.isDisjoint(with: targets) }
441+
}
442+
await scheduleIndexing(
443+
of: sourceFiles.keys,
444+
waitForBuildGraphGenerationTasks: true,
445+
indexFilesWithUpToDateUnit: false,
446+
priority: .low
447+
)
432448
}
433-
_ = await scheduleIndexing(
434-
of: sourceFiles.keys,
435-
indexFilesWithUpToDateUnit: false,
436-
priority: .low
437-
)
438449
}
439450
}
440451

@@ -696,12 +707,23 @@ package final actor SemanticIndexManager {
696707
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
697708
/// `indexFilesWithUpToDateUnit` is `true`.
698709
///
710+
/// If `waitForBuildGraphGenerationTasks` is `true` and there are any tasks in progress that wait for an up-to-date
711+
/// build graph, wait for those to finish. This is helpful so to avoid the following: We receive a build target change
712+
/// notification before the initial background indexing has finished. If indexstore-db hasn't been initialized with
713+
/// `pollForUnitChangesAndWait` yet, we might not know that the changed targets' files' index is actually up-to-date
714+
/// and would thus schedule an unnecessary re-index of the file.
715+
///
699716
/// The returned task finishes when all files are indexed.
717+
@discardableResult
700718
package func scheduleIndexing(
701719
of files: some Collection<DocumentURI> & Sendable,
720+
waitForBuildGraphGenerationTasks: Bool,
702721
indexFilesWithUpToDateUnit: Bool,
703722
priority: TaskPriority?
704723
) async -> Task<Void, Never> {
724+
if waitForBuildGraphGenerationTasks {
725+
await self.waitForBuildGraphGenerationTasks()
726+
}
705727
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
706728
// prepare and index operation at all.
707729
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule

Sources/SourceKitLSP/Workspace.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
175175
}
176176
if let semanticIndexManager {
177177
await semanticIndexManager.scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
178-
filesToIndex: nil,
179-
ensureAllUnitsRegisteredInIndex: true,
180178
indexFilesWithUpToDateUnit: false
181179
)
182180
}

0 commit comments

Comments
 (0)