@@ -178,7 +178,11 @@ package final actor SemanticIndexManager {
178
178
179
179
/// The tasks to generate the build graph (resolving package dependencies, generating the build description,
180
180
/// ...) 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 > ] = [ : ]
182
186
183
187
private let preparationUpToDateTracker = UpToDateTracker < BuildTargetIdentifier , DummySecondaryKey > ( )
184
188
@@ -245,7 +249,7 @@ package final actor SemanticIndexManager {
245
249
// Only report the `schedulingIndexing` status when we don't have any in-progress indexing tasks. This way we avoid
246
250
// flickering between indexing progress and `Scheduling indexing` if we trigger an index schedule task while
247
251
// indexing is already in progress
248
- if !scheduleIndexingTasks . isEmpty {
252
+ if !buildGraphGenerationTasks . isEmpty {
249
253
return . schedulingIndexing
250
254
}
251
255
return . upToDate
@@ -276,46 +280,37 @@ package final actor SemanticIndexManager {
276
280
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
277
281
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
278
282
///
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
283
284
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
284
285
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
285
286
/// 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 {
291
288
let taskId = UUID ( )
292
289
let generateBuildGraphTask = Task ( priority: . low) {
293
290
await withLoggingSubsystemAndScope ( subsystem: indexLoggingSubsystem, scope: " build-graph-generation " ) {
294
291
await hooks. buildGraphGenerationDidStart ? ( )
295
292
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 ( )
299
297
await hooks. buildGraphGenerationDidFinish ? ( )
300
298
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
301
299
// https://github.com/swiftlang/swift/issues/75602
302
300
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 (
311
305
of: filesToIndex,
306
+ waitForBuildGraphGenerationTasks: false ,
312
307
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
313
308
priority: . low
314
309
)
315
- scheduleIndexingTasks [ taskId] = nil
310
+ buildGraphGenerationTasks [ taskId] = nil
316
311
}
317
312
}
318
- scheduleIndexingTasks [ taskId] = generateBuildGraphTask
313
+ buildGraphGenerationTasks [ taskId] = generateBuildGraphTask
319
314
indexProgressStatusDidChange ( )
320
315
}
321
316
@@ -324,15 +319,11 @@ package final actor SemanticIndexManager {
324
319
package func scheduleReindex( ) async {
325
320
await indexStoreUpToDateTracker. markAllKnownOutOfDate ( )
326
321
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
327
- await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles (
328
- filesToIndex: nil ,
329
- ensureAllUnitsRegisteredInIndex: false ,
330
- indexFilesWithUpToDateUnit: true
331
- )
322
+ await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles ( indexFilesWithUpToDateUnit: true )
332
323
}
333
324
334
325
private func waitForBuildGraphGenerationTasks( ) async {
335
- await scheduleIndexingTasks . values. concurrentForEach { await $0. value }
326
+ await buildGraphGenerationTasks . values. concurrentForEach { await $0. value }
336
327
}
337
328
338
329
/// Wait for all in-progress index tasks to finish.
@@ -342,6 +333,8 @@ package final actor SemanticIndexManager {
342
333
// can await the index tasks below.
343
334
await waitForBuildGraphGenerationTasks ( )
344
335
336
+ await fileOrBuildTargetChangedTasks. concurrentForEach { await $0. value. value }
337
+
345
338
await inProgressIndexTasks. concurrentForEach { ( _, inProgress) in
346
339
switch inProgress. state {
347
340
case . creatingIndexTask:
@@ -364,14 +357,16 @@ package final actor SemanticIndexManager {
364
357
logger. info (
365
358
" Waiting for up-to-date index for \( uris. map { $0. fileURL? . lastPathComponent ?? $0. stringValue } . joined ( separator: " , " ) ) "
366
359
)
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 ( )
370
360
371
361
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
372
362
// - Wait for the existing index operations to finish if they have the same number of files.
373
363
// - 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
375
370
index. pollForUnitChangesAndWait ( )
376
371
logger. debug ( " Done waiting for up-to-date index " )
377
372
}
@@ -405,11 +400,19 @@ package final actor SemanticIndexManager {
405
400
await preparationUpToDateTracker. markOutOfDate ( outOfDateTargets)
406
401
}
407
402
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
+ }
413
416
}
414
417
415
418
package func buildTargetsChanged( _ changes: [ BuildTargetEvent ] ? ) async {
@@ -436,16 +439,24 @@ package final actor SemanticIndexManager {
436
439
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
437
440
}
438
441
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
+ )
443
459
}
444
- _ = await scheduleIndexing (
445
- of: sourceFiles. keys,
446
- indexFilesWithUpToDateUnit: false ,
447
- priority: . low
448
- )
449
460
}
450
461
}
451
462
@@ -748,12 +759,23 @@ package final actor SemanticIndexManager {
748
759
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
749
760
/// `indexFilesWithUpToDateUnit` is `true`.
750
761
///
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
+ ///
751
768
/// The returned task finishes when all files are indexed.
769
+ @discardableResult
752
770
package func scheduleIndexing(
753
771
of files: some Collection < DocumentURI > & Sendable ,
772
+ waitForBuildGraphGenerationTasks: Bool ,
754
773
indexFilesWithUpToDateUnit: Bool ,
755
774
priority: TaskPriority ?
756
775
) async -> Task < Void , Never > {
776
+ if waitForBuildGraphGenerationTasks {
777
+ await self . waitForBuildGraphGenerationTasks ( )
778
+ }
757
779
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
758
780
// prepare and index operation at all.
759
781
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
0 commit comments