@@ -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
@@ -225,7 +229,7 @@ package final actor SemanticIndexManager {
225
229
if inProgressPreparationTasks. values. contains ( where: { $0. purpose == . forEditorFunctionality } ) {
226
230
return . preparingFileForEditorFunctionality
227
231
}
228
- if !scheduleIndexingTasks . isEmpty {
232
+ if !buildGraphGenerationTasks . isEmpty {
229
233
return . schedulingIndexing
230
234
}
231
235
let preparationTasks = inProgressPreparationTasks. mapValues { inProgressTask in
@@ -270,46 +274,37 @@ package final actor SemanticIndexManager {
270
274
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
271
275
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
272
276
///
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
277
278
/// system but if existing unit files are not known to the index, we might re-index those files even if they are
278
279
/// up-to-date. Generally this should be set to `true` during the initial indexing (in which case we might be need to
279
280
/// 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 {
285
282
let taskId = UUID ( )
286
283
let generateBuildGraphTask = Task ( priority: . low) {
287
284
await withLoggingSubsystemAndScope ( subsystem: indexLoggingSubsystem, scope: " build-graph-generation " ) {
288
285
await hooks. buildGraphGenerationDidStart ? ( )
289
286
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 ( )
293
291
await hooks. buildGraphGenerationDidFinish ? ( )
294
292
// TODO: Ideally this would be a type like any Collection<DocumentURI> & Sendable but that doesn't work due to
295
293
// https://github.com/swiftlang/swift/issues/75602
296
294
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 (
305
299
of: filesToIndex,
300
+ waitForBuildGraphGenerationTasks: false ,
306
301
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
307
302
priority: . low
308
303
)
309
- scheduleIndexingTasks [ taskId] = nil
304
+ buildGraphGenerationTasks [ taskId] = nil
310
305
}
311
306
}
312
- scheduleIndexingTasks [ taskId] = generateBuildGraphTask
307
+ buildGraphGenerationTasks [ taskId] = generateBuildGraphTask
313
308
indexProgressStatusDidChange ( )
314
309
}
315
310
@@ -318,15 +313,11 @@ package final actor SemanticIndexManager {
318
313
package func scheduleReindex( ) async {
319
314
await indexStoreUpToDateTracker. markAllKnownOutOfDate ( )
320
315
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
321
- await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles (
322
- filesToIndex: nil ,
323
- ensureAllUnitsRegisteredInIndex: false ,
324
- indexFilesWithUpToDateUnit: true
325
- )
316
+ await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles ( indexFilesWithUpToDateUnit: true )
326
317
}
327
318
328
319
private func waitForBuildGraphGenerationTasks( ) async {
329
- await scheduleIndexingTasks . values. concurrentForEach { await $0. value }
320
+ await buildGraphGenerationTasks . values. concurrentForEach { await $0. value }
330
321
}
331
322
332
323
/// Wait for all in-progress index tasks to finish.
@@ -336,6 +327,8 @@ package final actor SemanticIndexManager {
336
327
// can await the index tasks below.
337
328
await waitForBuildGraphGenerationTasks ( )
338
329
330
+ await fileOrBuildTargetChangedTasks. concurrentForEach { await $0. value. value }
331
+
339
332
await inProgressIndexTasks. concurrentForEach { ( _, inProgress) in
340
333
switch inProgress. state {
341
334
case . creatingIndexTask:
@@ -358,14 +351,16 @@ package final actor SemanticIndexManager {
358
351
logger. info (
359
352
" Waiting for up-to-date index for \( uris. map { $0. fileURL? . lastPathComponent ?? $0. stringValue } . joined ( separator: " , " ) ) "
360
353
)
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 ( )
364
354
365
355
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
366
356
// - Wait for the existing index operations to finish if they have the same number of files.
367
357
// - 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
369
364
index. pollForUnitChangesAndWait ( )
370
365
logger. debug ( " Done waiting for up-to-date index " )
371
366
}
@@ -399,11 +394,19 @@ package final actor SemanticIndexManager {
399
394
await preparationUpToDateTracker. markOutOfDate ( outOfDateTargets)
400
395
}
401
396
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
+ }
407
410
}
408
411
409
412
package func buildTargetsChanged( _ changes: [ BuildTargetEvent ] ? ) async {
@@ -425,16 +428,24 @@ package final actor SemanticIndexManager {
425
428
await preparationUpToDateTracker. markAllKnownOutOfDate ( )
426
429
}
427
430
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
+ )
432
448
}
433
- _ = await scheduleIndexing (
434
- of: sourceFiles. keys,
435
- indexFilesWithUpToDateUnit: false ,
436
- priority: . low
437
- )
438
449
}
439
450
}
440
451
@@ -696,12 +707,23 @@ package final actor SemanticIndexManager {
696
707
/// known to be up-to-date based on `indexStoreUpToDateTracker` or the unit timestamp will not be re-indexed unless
697
708
/// `indexFilesWithUpToDateUnit` is `true`.
698
709
///
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
+ ///
699
716
/// The returned task finishes when all files are indexed.
717
+ @discardableResult
700
718
package func scheduleIndexing(
701
719
of files: some Collection < DocumentURI > & Sendable ,
720
+ waitForBuildGraphGenerationTasks: Bool ,
702
721
indexFilesWithUpToDateUnit: Bool ,
703
722
priority: TaskPriority ?
704
723
) async -> Task < Void , Never > {
724
+ if waitForBuildGraphGenerationTasks {
725
+ await self . waitForBuildGraphGenerationTasks ( )
726
+ }
705
727
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
706
728
// prepare and index operation at all.
707
729
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
0 commit comments