@@ -52,42 +52,53 @@ private struct OpaqueQueuedIndexTask: Equatable {
52
52
}
53
53
}
54
54
55
- private enum InProgressIndexStore {
56
- /// We know that we need to index the file but and are currently gathering all information to create the `indexTask`
57
- /// that will index it.
58
- ///
59
- /// This is needed to avoid the following race: We request indexing of file A. Getting the canonical target for A
60
- /// takes a bit and before that finishes, we request another index of A. In this case, we don't want to kick off
61
- /// two tasks to update the index store.
62
- case creatingIndexTask
55
+ private struct InProgressIndexStore {
56
+ enum State {
57
+ /// We know that we need to index the file but and are currently gathering all information to create the `indexTask`
58
+ /// that will index it.
59
+ ///
60
+ /// This is needed to avoid the following race: We request indexing of file A. Getting the canonical target for A
61
+ /// takes a bit and before that finishes, we request another index of A. In this case, we don't want to kick off
62
+ /// two tasks to update the index store.
63
+ case creatingIndexTask
64
+
65
+ /// We are waiting for preparation of the file's target to be scheduled. The next step is that we wait for
66
+ /// preparation to finish before we can update the index store for this file.
67
+ ///
68
+ /// `preparationTaskID` identifies the preparation task so that we can transition a file's index state to
69
+ /// `updatingIndexStore` when its preparation task has finished.
70
+ ///
71
+ /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
72
+ /// task is still the sole owner of it and responsible for its cancellation.
73
+ case waitingForPreparation( preparationTaskID: UUID , indexTask: Task < Void , Never > )
74
+
75
+ /// We have started preparing this file and are waiting for preparation to finish before we can update the index
76
+ /// store for this file.
77
+ ///
78
+ /// `preparationTaskID` identifies the preparation task so that we can transition a file's index state to
79
+ /// `updatingIndexStore` when its preparation task has finished.
80
+ ///
81
+ /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
82
+ /// task is still the sole owner of it and responsible for its cancellation.
83
+ case preparing( preparationTaskID: UUID , indexTask: Task < Void , Never > )
84
+
85
+ /// The file's target has been prepared and we are updating the file's index store.
86
+ ///
87
+ /// `updateIndexStoreTask` is the task that updates the index store itself.
88
+ ///
89
+ /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
90
+ /// task is still the sole owner of it and responsible for its cancellation.
91
+ case updatingIndexStore( updateIndexStoreTask: OpaqueQueuedIndexTask , indexTask: Task < Void , Never > )
92
+ }
63
93
64
- /// We are waiting for preparation of the file's target to be scheduled. The next step is that we wait for
65
- /// preparation to finish before we can update the index store for this file.
66
- ///
67
- /// `preparationTaskID` identifies the preparation task so that we can transition a file's index state to
68
- /// `updatingIndexStore` when its preparation task has finished.
69
- ///
70
- /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
71
- /// task is still the sole owner of it and responsible for its cancellation.
72
- case waitingForPreparation( preparationTaskID: UUID , indexTask: Task < Void , Never > )
94
+ var state : State
73
95
74
- /// We have started preparing this file and are waiting for preparation to finish before we can update the index
75
- /// store for this file.
76
- ///
77
- /// `preparationTaskID` identifies the preparation task so that we can transition a file's index state to
78
- /// `updatingIndexStore` when its preparation task has finished.
96
+ /// The modification time of the time of `FileToIndex.sourceFile` at the time that indexing was scheduled. This allows
97
+ /// us to avoid scheduling another indexing operation for the file if the file hasn't been modified since an
98
+ /// in-progress indexing operation was scheduled.
79
99
///
80
- /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
81
- /// task is still the sole owner of it and responsible for its cancellation.
82
- case preparing( preparationTaskID: UUID , indexTask: Task < Void , Never > )
83
-
84
- /// The file's target has been prepared and we are updating the file's index store.
85
- ///
86
- /// `updateIndexStoreTask` is the task that updates the index store itself.
87
- ///
88
- /// `indexTask` is a task that finishes after both preparation and index store update are done. Whoever owns the index
89
- /// task is still the sole owner of it and responsible for its cancellation.
90
- case updatingIndexStore( updateIndexStoreTask: OpaqueQueuedIndexTask , indexTask: Task < Void , Never > )
100
+ /// `nil` if the modification date of the file could not be determined.
101
+ var fileModificationDate : Date ?
91
102
}
92
103
93
104
/// Status of document indexing / target preparation in `inProgressIndexAndPreparationTasks`.
@@ -229,8 +240,8 @@ package final actor SemanticIndexManager {
229
240
let preparationTasks = inProgressPreparationTasks. mapValues { inProgressTask in
230
241
return inProgressTask. task. isExecuting ? IndexTaskStatus . executing : IndexTaskStatus . scheduled
231
242
}
232
- let indexTasks = inProgressIndexTasks. mapValues { status in
233
- switch status {
243
+ let indexTasks = inProgressIndexTasks. mapValues { inProgress in
244
+ switch inProgress . state {
234
245
case . creatingIndexTask, . waitingForPreparation, . preparing:
235
246
return IndexTaskStatus . scheduled
236
247
case . updatingIndexStore( updateIndexStoreTask: let updateIndexStoreTask, indexTask: _) :
@@ -333,8 +344,8 @@ package final actor SemanticIndexManager {
333
344
// can await the index tasks below.
334
345
await waitForBuildGraphGenerationTasks ( )
335
346
336
- await inProgressIndexTasks. concurrentForEach { ( _, status ) in
337
- switch status {
347
+ await inProgressIndexTasks. concurrentForEach { ( _, inProgress ) in
348
+ switch inProgress . state {
338
349
case . creatingIndexTask:
339
350
break
340
351
case . waitingForPreparation( preparationTaskID: _, indexTask: let indexTask) ,
@@ -413,7 +424,7 @@ package final actor SemanticIndexManager {
413
424
private func filesToIndex(
414
425
toCover files: some Collection < DocumentURI > & Sendable ,
415
426
indexFilesWithUpToDateUnits: Bool
416
- ) async -> [ FileToIndex ] {
427
+ ) async -> [ ( file : FileToIndex , fileModificationDate : Date ? ) ] {
417
428
let sourceFiles = await orLog ( " Getting source files in project " ) {
418
429
Set ( try await buildSystemManager. sourceFiles ( includeNonBuildableFiles: false ) . keys)
419
430
}
@@ -422,6 +433,7 @@ package final actor SemanticIndexManager {
422
433
}
423
434
let deletedFilesIndex = index. checked ( for: . deletedFiles)
424
435
let modifiedFilesIndex = index. checked ( for: . modifiedFiles)
436
+
425
437
let filesToReIndex =
426
438
await files
427
439
. asyncFilter {
@@ -431,13 +443,13 @@ package final actor SemanticIndexManager {
431
443
return false
432
444
}
433
445
return true
434
- } . compactMap { ( uri) -> FileToIndex ? in
446
+ } . compactMap { uri -> ( FileToIndex , Date ? ) ? in
435
447
if sourceFiles. contains ( uri) {
436
448
if !indexFilesWithUpToDateUnits, modifiedFilesIndex. hasUpToDateUnit ( for: uri) {
437
449
return nil
438
450
}
439
451
// If this is a source file, just index it.
440
- return . indexableFile( uri)
452
+ return ( . indexableFile( uri) , modifiedFilesIndex . modificationDate ( of : uri ) )
441
453
}
442
454
// Otherwise, see if it is a header file. If so, index a main file that that imports it to update header file's
443
455
// index.
@@ -454,15 +466,7 @@ package final actor SemanticIndexManager {
454
466
if !indexFilesWithUpToDateUnits, modifiedFilesIndex. hasUpToDateUnit ( for: uri, mainFile: mainFile) {
455
467
return nil
456
468
}
457
- return . headerFile( header: uri, mainFile: mainFile)
458
- }
459
- . filter {
460
- switch inProgressIndexTasks [ $0] {
461
- case . waitingForPreparation, . creatingIndexTask:
462
- return false
463
- default :
464
- return true
465
- }
469
+ return ( . headerFile( header: uri, mainFile: mainFile) , modifiedFilesIndex. modificationDate ( of: uri) )
466
470
}
467
471
return filesToReIndex
468
472
}
@@ -640,7 +644,7 @@ package final actor SemanticIndexManager {
640
644
return
641
645
}
642
646
for fileAndTarget in filesAndTargets {
643
- switch self . inProgressIndexTasks [ fileAndTarget. file] {
647
+ switch self . inProgressIndexTasks [ fileAndTarget. file] ? . state {
644
648
case . updatingIndexStore( let registeredTask, _) :
645
649
if registeredTask == OpaqueQueuedIndexTask ( task) {
646
650
self . inProgressIndexTasks [ fileAndTarget. file] = nil
@@ -656,9 +660,9 @@ package final actor SemanticIndexManager {
656
660
self . indexProgressStatusDidChange ( )
657
661
}
658
662
for fileAndTarget in filesAndTargets {
659
- switch inProgressIndexTasks [ fileAndTarget. file] {
663
+ switch inProgressIndexTasks [ fileAndTarget. file] ? . state {
660
664
case . waitingForPreparation( preparationTaskID, let indexTask) , . preparing( preparationTaskID, let indexTask) :
661
- inProgressIndexTasks [ fileAndTarget. file] = . updatingIndexStore(
665
+ inProgressIndexTasks [ fileAndTarget. file] ? . state = . updatingIndexStore(
662
666
updateIndexStoreTask: OpaqueQueuedIndexTask ( updateIndexTask) ,
663
667
indexTask: indexTask
664
668
)
@@ -683,34 +687,62 @@ package final actor SemanticIndexManager {
683
687
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
684
688
// schedule two indexing jobs for the same file in quick succession, only the first one actually updates the index
685
689
// store and the second one will be a no-op once it runs.
686
- let outOfDateFiles = await filesToIndex ( toCover: files, indexFilesWithUpToDateUnits: indexFilesWithUpToDateUnit)
690
+ var filesToIndex = await filesToIndex ( toCover: files, indexFilesWithUpToDateUnits: indexFilesWithUpToDateUnit)
687
691
// sort files to get deterministic indexing order
688
- . sorted ( by: { $0. sourceFile. stringValue < $1. sourceFile. stringValue } )
689
-
690
- if outOfDateFiles. isEmpty {
691
- // Early exit if there are no files to index.
692
- return Task { }
693
- }
694
-
695
- logger. debug ( " Scheduling indexing of \( outOfDateFiles. map ( \. sourceFile. stringValue) . joined ( separator: " , " ) ) " )
696
-
697
- // Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us
698
- // to index the low-level targets ASAP.
699
- var filesByTarget : [ BuildTargetIdentifier : [ FileToIndex ] ] = [ : ]
692
+ . sorted ( by: { $0. file. sourceFile. stringValue < $1. file. sourceFile. stringValue } )
700
693
701
694
// The number of index tasks that don't currently have an in-progress task associated with it.
702
695
// The denominator in the index progress should get incremented by this amount.
703
696
// We don't want to increment the denominator for tasks that already have an index in progress.
704
697
var newIndexTasks = 0
705
- for file in outOfDateFiles {
706
- if inProgressIndexTasks [ file] == nil {
698
+ var alreadyScheduledTasks : Set < FileToIndex > = [ ]
699
+ for file in filesToIndex {
700
+ let inProgress = inProgressIndexTasks [ file. file]
701
+
702
+ let shouldScheduleIndexing : Bool
703
+ switch inProgress? . state {
704
+ case nil :
707
705
newIndexTasks += 1
706
+ shouldScheduleIndexing = true
707
+ case . creatingIndexTask, . waitingForPreparation:
708
+ // We already have a task that indexes the file but hasn't started preparation yet. Indexing the file again
709
+ // won't produce any new results.
710
+ alreadyScheduledTasks. insert ( file. file)
711
+ shouldScheduleIndexing = false
712
+ case . preparing( _, _) , . updatingIndexStore( _, _) :
713
+ // We have started indexing of the file and are now requesting to index it again. Unless we know that the file
714
+ // hasn't been modified since the last request for indexing, we need to schedule it to get re-indexed again.
715
+ if let modDate = file. fileModificationDate, inProgress? . fileModificationDate == modDate {
716
+ shouldScheduleIndexing = false
717
+ } else {
718
+ shouldScheduleIndexing = true
719
+ }
720
+ }
721
+ if shouldScheduleIndexing {
722
+ inProgressIndexTasks [ file. file] = InProgressIndexStore (
723
+ state: . creatingIndexTask,
724
+ fileModificationDate: file. fileModificationDate
725
+ )
726
+ } else {
727
+ alreadyScheduledTasks. insert ( file. file)
728
+
708
729
}
709
- inProgressIndexTasks [ file] = . creatingIndexTask
710
730
}
711
731
indexTasksWereScheduled ( newIndexTasks)
732
+ filesToIndex = filesToIndex. filter { !alreadyScheduledTasks. contains ( $0. file) }
733
+
734
+ if filesToIndex. isEmpty {
735
+ // Early exit if there are no files to index.
736
+ return Task { }
737
+ }
738
+
739
+ logger. debug ( " Scheduling indexing of \( filesToIndex. map ( \. file. sourceFile. stringValue) . joined ( separator: " , " ) ) " )
740
+
741
+ // Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us
742
+ // to index the low-level targets ASAP.
743
+ var filesByTarget : [ BuildTargetIdentifier : [ ( FileToIndex ) ] ] = [ : ]
712
744
713
- for fileToIndex in outOfDateFiles {
745
+ for fileToIndex in filesToIndex . map ( \ . file ) {
714
746
guard let target = await buildSystemManager. canonicalTarget ( for: fileToIndex. mainFile) else {
715
747
logger. error (
716
748
" Not indexing \( fileToIndex. forLogging) because the target could not be determined "
@@ -757,9 +789,9 @@ package final actor SemanticIndexManager {
757
789
if case . executing = newState {
758
790
for file in filesToIndex {
759
791
if case . waitingForPreparation( preparationTaskID: preparationTaskID, indexTask: let indexTask) =
760
- self . inProgressIndexTasks [ file]
792
+ self . inProgressIndexTasks [ file] ? . state
761
793
{
762
- self . inProgressIndexTasks [ file] = . preparing(
794
+ self . inProgressIndexTasks [ file] ? . state = . preparing(
763
795
preparationTaskID: preparationTaskID,
764
796
indexTask: indexTask
765
797
)
@@ -796,7 +828,7 @@ package final actor SemanticIndexManager {
796
828
// `.waitingForPreparation` here because we don't have an `await` call between the creation of `indexTask` and
797
829
// this loop, so we still have exclusive access to the `SemanticIndexManager` actor and hence `updateIndexStore`
798
830
// can't execute until we have set all index statuses to `.waitingForPreparation`.
799
- inProgressIndexTasks [ file] = . waitingForPreparation(
831
+ inProgressIndexTasks [ file] ? . state = . waitingForPreparation(
800
832
preparationTaskID: preparationTaskID,
801
833
indexTask: indexTask
802
834
)
0 commit comments