Skip to content

Commit e474354

Browse files
authored
Merge pull request #2051 from ahoppen/build-graph-generation
Don't wait for build graph generation when file is changed
2 parents 4883ee7 + dcad3f4 commit e474354

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
@@ -178,7 +178,11 @@ package final actor SemanticIndexManager {
178178

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

183187
private let preparationUpToDateTracker = UpToDateTracker<BuildTargetIdentifier, DummySecondaryKey>()
184188

@@ -245,7 +249,7 @@ package final actor SemanticIndexManager {
245249
// Only report the `schedulingIndexing` status when we don't have any in-progress indexing tasks. This way we avoid
246250
// flickering between indexing progress and `Scheduling indexing` if we trigger an index schedule task while
247251
// indexing is already in progress
248-
if !scheduleIndexingTasks.isEmpty {
252+
if !buildGraphGenerationTasks.isEmpty {
249253
return .schedulingIndexing
250254
}
251255
return .upToDate
@@ -276,46 +280,37 @@ package final actor SemanticIndexManager {
276280
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
277281
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
278282
///
279-
/// If `filesToIndex` is `nil`, all files in the build system with out-of-date units are indexed.
280-
///
281-
/// If `ensureAllUnitsRegisteredInIndex` is `true`, ensure that all units are registered in the index before
282-
/// triggering the indexing. This is a costly operation since it iterates through all the unit files on the file
283+
/// This is a costly operation since it iterates through all the unit files on the file
283284
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
284285
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
285286
/// build the indexstore-db) and `false` for all subsequent indexing.
286-
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
287-
filesToIndex: [DocumentURI]?,
288-
ensureAllUnitsRegisteredInIndex: Bool,
289-
indexFilesWithUpToDateUnit: Bool
290-
) async {
287+
package func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: Bool) async {
291288
let taskId = UUID()
292289
let generateBuildGraphTask = Task(priority: .low) {
293290
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "build-graph-generation") {
294291
await hooks.buildGraphGenerationDidStart?()
295292
await self.buildSystemManager.waitForUpToDateBuildGraph()
296-
if ensureAllUnitsRegisteredInIndex {
297-
index.pollForUnitChangesAndWait()
298-
}
293+
// Polling for unit changes is a costly operation since it iterates through all the unit files on the file
294+
// system but if existing unit files are not known to the index, we might re-index those files even if they are
295+
// up-to-date. This operation is worth the cost during initial indexing and during the manual re-index command.
296+
index.pollForUnitChangesAndWait()
299297
await hooks.buildGraphGenerationDidFinish?()
300298
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
301299
// https://github.com/swiftlang/swift/issues/75602
302300
let filesToIndex: [DocumentURI] =
303-
if let filesToIndex {
304-
filesToIndex
305-
} else {
306-
await orLog("Getting files to index") {
307-
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
308-
} ?? []
309-
}
310-
_ = await self.scheduleIndexing(
301+
await orLog("Getting files to index") {
302+
try await self.buildSystemManager.buildableSourceFiles().sorted { $0.stringValue < $1.stringValue }
303+
} ?? []
304+
await self.scheduleIndexing(
311305
of: filesToIndex,
306+
waitForBuildGraphGenerationTasks: false,
312307
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
313308
priority: .low
314309
)
315-
scheduleIndexingTasks[taskId] = nil
310+
buildGraphGenerationTasks[taskId] = nil
316311
}
317312
}
318-
scheduleIndexingTasks[taskId] = generateBuildGraphTask
313+
buildGraphGenerationTasks[taskId] = generateBuildGraphTask
319314
indexProgressStatusDidChange()
320315
}
321316

@@ -324,15 +319,11 @@ package final actor SemanticIndexManager {
324319
package func scheduleReindex() async {
325320
await indexStoreUpToDateTracker.markAllKnownOutOfDate()
326321
await preparationUpToDateTracker.markAllKnownOutOfDate()
327-
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
328-
filesToIndex: nil,
329-
ensureAllUnitsRegisteredInIndex: false,
330-
indexFilesWithUpToDateUnit: true
331-
)
322+
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: true)
332323
}
333324

334325
private func waitForBuildGraphGenerationTasks() async {
335-
await scheduleIndexingTasks.values.concurrentForEach { await $0.value }
326+
await buildGraphGenerationTasks.values.concurrentForEach { await $0.value }
336327
}
337328

338329
/// Wait for all in-progress index tasks to finish.
@@ -342,6 +333,8 @@ package final actor SemanticIndexManager {
342333
// can await the index tasks below.
343334
await waitForBuildGraphGenerationTasks()
344335

336+
await fileOrBuildTargetChangedTasks.concurrentForEach { await $0.value.value }
337+
345338
await inProgressIndexTasks.concurrentForEach { (_, inProgress) in
346339
switch inProgress.state {
347340
case .creatingIndexTask:
@@ -364,14 +357,16 @@ package final actor SemanticIndexManager {
364357
logger.info(
365358
"Waiting for up-to-date index for \(uris.map { $0.fileURL?.lastPathComponent ?? $0.stringValue }.joined(separator: ", "))"
366359
)
367-
// If there's a build graph update in progress wait for that to finish so we can discover new files in the build
368-
// system.
369-
await waitForBuildGraphGenerationTasks()
370360

371361
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
372362
// - Wait for the existing index operations to finish if they have the same number of files.
373363
// - Reschedule the background index task in favor of an index task with fewer source files.
374-
await self.scheduleIndexing(of: uris, indexFilesWithUpToDateUnit: false, priority: nil).value
364+
await self.scheduleIndexing(
365+
of: uris,
366+
waitForBuildGraphGenerationTasks: true,
367+
indexFilesWithUpToDateUnit: false,
368+
priority: nil
369+
).value
375370
index.pollForUnitChangesAndWait()
376371
logger.debug("Done waiting for up-to-date index")
377372
}
@@ -405,11 +400,19 @@ package final actor SemanticIndexManager {
405400
await preparationUpToDateTracker.markOutOfDate(outOfDateTargets)
406401
}
407402

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

415418
package func buildTargetsChanged(_ changes: [BuildTargetEvent]?) async {
@@ -436,16 +439,24 @@ package final actor SemanticIndexManager {
436439
await preparationUpToDateTracker.markAllKnownOutOfDate()
437440
}
438441

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

@@ -748,12 +759,23 @@ package final actor SemanticIndexManager {
748759
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
749760
/// `indexFilesWithUpToDateUnit` is `true`.
750761
///
762+
/// If `waitForBuildGraphGenerationTasks` is `true` and there are any tasks in progress that wait for an up-to-date
763+
/// build graph, wait for those to finish. This is helpful so to avoid the following: We receive a build target change
764+
/// notification before the initial background indexing has finished. If indexstore-db hasn't been initialized with
765+
/// `pollForUnitChangesAndWait` yet, we might not know that the changed targets' files' index is actually up-to-date
766+
/// and would thus schedule an unnecessary re-index of the file.
767+
///
751768
/// The returned task finishes when all files are indexed.
769+
@discardableResult
752770
package func scheduleIndexing(
753771
of files: some Collection<DocumentURI> & Sendable,
772+
waitForBuildGraphGenerationTasks: Bool,
754773
indexFilesWithUpToDateUnit: Bool,
755774
priority: TaskPriority?
756775
) async -> Task<Void, Never> {
776+
if waitForBuildGraphGenerationTasks {
777+
await self.waitForBuildGraphGenerationTasks()
778+
}
757779
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
758780
// prepare and index operation at all.
759781
// 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
@@ -181,8 +181,6 @@ package final class Workspace: Sendable, BuildSystemManagerDelegate {
181181
}
182182
if let semanticIndexManager {
183183
await semanticIndexManager.scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(
184-
filesToIndex: nil,
185-
ensureAllUnitsRegisteredInIndex: true,
186184
indexFilesWithUpToDateUnit: false
187185
)
188186
}

0 commit comments

Comments
 (0)