@@ -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
0 commit comments