@@ -175,7 +175,11 @@ package final actor SemanticIndexManager {
175
175
176
176
/// The tasks to generate the build graph (resolving package dependencies, generating the build description,
177
177
/// ...) 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 > ] = [ : ]
179
183
180
184
private let preparationUpToDateTracker = UpToDateTracker < BuildTargetIdentifier > ( )
181
185
@@ -242,7 +246,7 @@ package final actor SemanticIndexManager {
242
246
// Only report the `schedulingIndexing` status when we don't have any in-progress indexing tasks. This way we avoid
243
247
// flickering between indexing progress and `Scheduling indexing` if we trigger an index schedule task while
244
248
// indexing is already in progress
245
- if !scheduleIndexingTasks . isEmpty {
249
+ if !buildGraphGenerationTasks . isEmpty {
246
250
return . schedulingIndexing
247
251
}
248
252
return . upToDate
@@ -273,46 +277,37 @@ package final actor SemanticIndexManager {
273
277
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
274
278
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
275
279
///
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
280
281
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
281
282
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
282
283
/// 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 {
288
285
let taskId = UUID ( )
289
286
let generateBuildGraphTask = Task ( priority: . low) {
290
287
await withLoggingSubsystemAndScope ( subsystem: indexLoggingSubsystem, scope: " build-graph-generation " ) {
291
288
await hooks. buildGraphGenerationDidStart ? ( )
292
289
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 ( )
296
294
await hooks. buildGraphGenerationDidFinish ? ( )
297
295
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
298
296
// https://github.com/swiftlang/swift/issues/75602
299
297
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 (
308
302
of: filesToIndex,
303
+ waitForBuildGraphGenerationTasks: false ,
309
304
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
310
305
priority: . low
311
306
)
312
- scheduleIndexingTasks [ taskId] = nil
307
+ buildGraphGenerationTasks [ taskId] = nil
313
308
}
314
309
}
315
- scheduleIndexingTasks [ taskId] = generateBuildGraphTask
310
+ buildGraphGenerationTasks [ taskId] = generateBuildGraphTask
316
311
indexProgressStatusDidChange ( )
317
312
}
318
313
@@ -321,15 +316,11 @@ package final actor SemanticIndexManager {
321
316
package func scheduleReindex( ) async {
322
317
await indexStoreUpToDateTracker. markAllKnownOutOfDate ( )
323
318
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
324
- await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles (
325
- filesToIndex: nil ,
326
- ensureAllUnitsRegisteredInIndex: false ,
327
- indexFilesWithUpToDateUnit: true
328
- )
319
+ await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles ( indexFilesWithUpToDateUnit: true )
329
320
}
330
321
331
322
private func waitForBuildGraphGenerationTasks( ) async {
332
- await scheduleIndexingTasks . values. concurrentForEach { await $0. value }
323
+ await buildGraphGenerationTasks . values. concurrentForEach { await $0. value }
333
324
}
334
325
335
326
/// Wait for all in-progress index tasks to finish.
@@ -339,6 +330,8 @@ package final actor SemanticIndexManager {
339
330
// can await the index tasks below.
340
331
await waitForBuildGraphGenerationTasks ( )
341
332
333
+ await fileOrBuildTargetChangedTasks. concurrentForEach { await $0. value. value }
334
+
342
335
await inProgressIndexTasks. concurrentForEach { ( _, inProgress) in
343
336
switch inProgress. state {
344
337
case . creatingIndexTask:
@@ -361,14 +354,16 @@ package final actor SemanticIndexManager {
361
354
logger. info (
362
355
" Waiting for up-to-date index for \( uris. map { $0. fileURL? . lastPathComponent ?? $0. stringValue } . joined ( separator: " , " ) ) "
363
356
)
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 ( )
367
357
368
358
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
369
359
// - Wait for the existing index operations to finish if they have the same number of files.
370
360
// - 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
372
367
index. pollForUnitChangesAndWait ( )
373
368
logger. debug ( " Done waiting for up-to-date index " )
374
369
}
@@ -402,11 +397,19 @@ package final actor SemanticIndexManager {
402
397
await preparationUpToDateTracker. markOutOfDate ( outOfDateTargets)
403
398
}
404
399
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
+ }
410
413
}
411
414
412
415
package func buildTargetsChanged( _ changes: [ BuildTargetEvent ] ? ) async {
@@ -428,16 +431,24 @@ package final actor SemanticIndexManager {
428
431
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
429
432
}
430
433
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
+ )
435
451
}
436
- _ = await scheduleIndexing (
437
- of: sourceFiles. keys,
438
- indexFilesWithUpToDateUnit: false ,
439
- priority: . low
440
- )
441
452
}
442
453
}
443
454
@@ -699,12 +710,23 @@ package final actor SemanticIndexManager {
699
710
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
700
711
/// `indexFilesWithUpToDateUnit` is `true`.
701
712
///
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
+ ///
702
719
/// The returned task finishes when all files are indexed.
720
+ @discardableResult
703
721
package func scheduleIndexing(
704
722
of files: some Collection < DocumentURI > & Sendable ,
723
+ waitForBuildGraphGenerationTasks: Bool ,
705
724
indexFilesWithUpToDateUnit: Bool ,
706
725
priority: TaskPriority ?
707
726
) async -> Task < Void , Never > {
727
+ if waitForBuildGraphGenerationTasks {
728
+ await self . waitForBuildGraphGenerationTasks ( )
729
+ }
708
730
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
709
731
// prepare and index operation at all.
710
732
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
0 commit comments