Skip to content

Commit bade2b7

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 swiftlang#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 956742d commit bade2b7

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

@@ -242,7 +246,7 @@ package final actor SemanticIndexManager {
242246
// Only report the `schedulingIndexing` status when we don't have any in-progress indexing tasks. This way we avoid
243247
// flickering between indexing progress and `Scheduling indexing` if we trigger an index schedule task while
244248
// indexing is already in progress
245-
if !scheduleIndexingTasks.isEmpty {
249+
if !buildGraphGenerationTasks.isEmpty {
246250
return .schedulingIndexing
247251
}
248252
return .upToDate
@@ -273,46 +277,37 @@ package final actor SemanticIndexManager {
273277
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
274278
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
275279
///
276-
/// If `filesToIndex` is `nil`, all files in the build system with out-of-date units are indexed.
277-
///
278-
/// If `ensureAllUnitsRegisteredInIndex` is `true`, ensure that all units are registered in the index before
279-
/// triggering the indexing. This is a costly operation since it iterates through all the unit files on the file
280+
/// This is a costly operation since it iterates through all the unit files on the file
280281
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
281282
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
282283
/// build the indexstore-db) and `false` for all subsequent indexing.
283-
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
284-
filesToIndex: [DocumentURI]?,
285-
ensureAllUnitsRegisteredInIndex: Bool,
286-
indexFilesWithUpToDateUnit: Bool
287-
) async {
284+
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: Bool) async {
288285
let taskId = UUID()
289286
let generateBuildGraphTask = Task(priority: .low) {
290287
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "build-graph-generation") {
291288
await hooks.buildGraphGenerationDidStart?()
292289
await self.buildSystemManager.waitForUpToDateBuildGraph()
293-
if ensureAllUnitsRegisteredInIndex {
294-
index.pollForUnitChangesAndWait()
295-
}
290+
// Polling for unit changes is a costly operation since it iterates through all the unit files on the file
291+
// system but if existing unit files are not known to the index, we might re-index those files even if they are
292+
// up-to-date. This operation is worth the cost during initial indexing and during the manual re-index command.
293+
index.pollForUnitChangesAndWait()
296294
await hooks.buildGraphGenerationDidFinish?()
297295
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
298296
// https://github.com/swiftlang/swift/issues/75602
299297
let filesToIndex: [DocumentURI] =
300-
if let filesToIndex {
301-
filesToIndex
302-
} else {
303-
await orLog("Getting files to index") {
304-
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
305-
} ?? []
306-
}
307-
_ = await self.scheduleIndexing(
298+
await orLog("Getting files to index") {
299+
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
300+
} ?? []
301+
await self.scheduleIndexing(
308302
of: filesToIndex,
303+
waitForBuildGraphGenerationTasks: false,
309304
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
310305
priority: .low
311306
)
312-
scheduleIndexingTasks[taskId] = nil
307+
buildGraphGenerationTasks[taskId] = nil
313308
}
314309
}
315-
scheduleIndexingTasks[taskId] = generateBuildGraphTask
310+
buildGraphGenerationTasks[taskId] = generateBuildGraphTask
316311
indexProgressStatusDidChange()
317312
}
318313

@@ -321,15 +316,11 @@ package final actor SemanticIndexManager {
321316
package func scheduleReindex() async {
322317
await indexStoreUpToDateTracker.markAllKnownOutOfDate()
323318
await preparationUpToDateTracker.markAllKnownOutOfDate()
324-
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
325-
filesToIndex: nil,
326-
ensureAllUnitsRegisteredInIndex: false,
327-
indexFilesWithUpToDateUnit: true
328-
)
319+
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: true)
329320
}
330321

331322
private func waitForBuildGraphGenerationTasks() async {
332-
await scheduleIndexingTasks.values.concurrentForEach { await $0.value }
323+
await buildGraphGenerationTasks.values.concurrentForEach { await $0.value }
333324
}
334325

335326
/// Wait for all in-progress index tasks to finish.
@@ -339,6 +330,8 @@ package final actor SemanticIndexManager {
339330
// can await the index tasks below.
340331
await waitForBuildGraphGenerationTasks()
341332

333+
await fileOrBuildTargetChangedTasks.concurrentForEach { await $0.value.value }
334+
342335
await inProgressIndexTasks.concurrentForEach { (_, inProgress) in
343336
switch inProgress.state {
344337
case .creatingIndexTask:
@@ -361,14 +354,16 @@ package final actor SemanticIndexManager {
361354
logger.info(
362355
"Waiting for up-to-date index for \(uris.map { $0.fileURL?.lastPathComponent ?? $0.stringValue }.joined(separator: ", "))"
363356
)
364-
// If there's a build graph update in progress wait for that to finish so we can discover new files in the build
365-
// system.
366-
await waitForBuildGraphGenerationTasks()
367357

368358
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
369359
// - Wait for the existing index operations to finish if they have the same number of files.
370360
// - Reschedule the background index task in favor of an index task with fewer source files.
371-
await self.scheduleIndexing(of: uris, indexFilesWithUpToDateUnit: false, priority: nil).value
361+
await self.scheduleIndexing(
362+
of: uris,
363+
waitForBuildGraphGenerationTasks: true,
364+
indexFilesWithUpToDateUnit: false,
365+
priority: nil
366+
).value
372367
index.pollForUnitChangesAndWait()
373368
logger.debug("Done waiting for up-to-date index")
374369
}
@@ -402,11 +397,19 @@ package final actor SemanticIndexManager {
402397
await preparationUpToDateTracker.markOutOfDate(outOfDateTargets)
403398
}
404399

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

412415
package func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async {
@@ -428,16 +431,24 @@ package final actor SemanticIndexManager {
428431
await preparationUpToDateTracker.markAllKnownOutOfDate()
429432
}
430433

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

@@ -699,12 +710,23 @@ package final actor SemanticIndexManager {
699710
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
700711
/// `indexFilesWithUpToDateUnit` is `true`.
701712
///
713+
/// If `waitForBuildGraphGenerationTasks` is `true` and there are any tasks in progress that wait for an up-to-date
714+
/// build graph, wait for those to finish. This is helpful so to avoid the following: We receive a build target change
715+
/// notification before the initial background indexing has finished. If indexstore-db hasn't been initialized with
716+
/// `pollForUnitChangesAndWait` yet, we might not know that the changed targets' files' index is actually up-to-date
717+
/// and would thus schedule an unnecessary re-index of the file.
718+
///
702719
/// The returned task finishes when all files are indexed.
720+
@discardableResult
703721
package func scheduleIndexing(
704722
of files: some Collection<DocumentURI> & Sendable,
723+
waitForBuildGraphGenerationTasks: Bool,
705724
indexFilesWithUpToDateUnit: Bool,
706725
priority: TaskPriority?
707726
) async -> Task<Void, Never> {
727+
if waitForBuildGraphGenerationTasks {
728+
await self.waitForBuildGraphGenerationTasks()
729+
}
708730
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
709731
// prepare and index operation at all.
710732
// 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)