@@ -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: _) :
@@ -340,8 +351,8 @@ package final actor SemanticIndexManager {
340
351
await waitForBuildGraphGenerationTasks ( )
341
352
342
353
await withTaskGroup ( of: Void . self) { taskGroup in
343
- for (_ , status ) in inProgressIndexTasks {
344
- switch status {
354
+ for inProgress in inProgressIndexTasks. values {
355
+ switch inProgress . state {
345
356
case . creatingIndexTask:
346
357
break
347
358
case . waitingForPreparation( preparationTaskID: _, indexTask: let indexTask) ,
@@ -424,7 +435,7 @@ package final actor SemanticIndexManager {
424
435
private func filesToIndex(
425
436
toCover files: some Collection < DocumentURI > & Sendable ,
426
437
indexFilesWithUpToDateUnits: Bool
427
- ) async -> [ FileToIndex ] {
438
+ ) async -> [ ( file : FileToIndex , fileModificationDate : Date ? ) ] {
428
439
let sourceFiles = await orLog ( " Getting source files in project " ) {
429
440
Set ( try await buildSystemManager. sourceFiles ( includeNonBuildableFiles: false ) . keys)
430
441
}
@@ -433,6 +444,7 @@ package final actor SemanticIndexManager {
433
444
}
434
445
let deletedFilesIndex = index. checked ( for: . deletedFiles)
435
446
let modifiedFilesIndex = index. checked ( for: . modifiedFiles)
447
+
436
448
let filesToReIndex =
437
449
await files
438
450
. asyncFilter {
@@ -442,13 +454,13 @@ package final actor SemanticIndexManager {
442
454
return false
443
455
}
444
456
return true
445
- } . compactMap { ( uri) -> FileToIndex ? in
457
+ } . compactMap { uri -> ( FileToIndex , Date ? ) ? in
446
458
if sourceFiles. contains ( uri) {
447
459
if !indexFilesWithUpToDateUnits, modifiedFilesIndex. hasUpToDateUnit ( for: uri) {
448
460
return nil
449
461
}
450
462
// If this is a source file, just index it.
451
- return . indexableFile( uri)
463
+ return ( . indexableFile( uri) , modifiedFilesIndex . modificationDate ( of : uri ) )
452
464
}
453
465
// Otherwise, see if it is a header file. If so, index a main file that that imports it to update header file's
454
466
// index.
@@ -465,15 +477,7 @@ package final actor SemanticIndexManager {
465
477
if !indexFilesWithUpToDateUnits, modifiedFilesIndex. hasUpToDateUnit ( for: uri, mainFile: mainFile) {
466
478
return nil
467
479
}
468
- return . headerFile( header: uri, mainFile: mainFile)
469
- }
470
- . filter {
471
- switch inProgressIndexTasks [ $0] {
472
- case . waitingForPreparation, . creatingIndexTask:
473
- return false
474
- default :
475
- return true
476
- }
480
+ return ( . headerFile( header: uri, mainFile: mainFile) , modifiedFilesIndex. modificationDate ( of: uri) )
477
481
}
478
482
return filesToReIndex
479
483
}
@@ -651,7 +655,7 @@ package final actor SemanticIndexManager {
651
655
return
652
656
}
653
657
for fileAndTarget in filesAndTargets {
654
- switch self . inProgressIndexTasks [ fileAndTarget. file] {
658
+ switch self . inProgressIndexTasks [ fileAndTarget. file] ? . state {
655
659
case . updatingIndexStore( let registeredTask, _) :
656
660
if registeredTask == OpaqueQueuedIndexTask ( task) {
657
661
self . inProgressIndexTasks [ fileAndTarget. file] = nil
@@ -667,9 +671,9 @@ package final actor SemanticIndexManager {
667
671
self . indexProgressStatusDidChange ( )
668
672
}
669
673
for fileAndTarget in filesAndTargets {
670
- switch inProgressIndexTasks [ fileAndTarget. file] {
674
+ switch inProgressIndexTasks [ fileAndTarget. file] ? . state {
671
675
case . waitingForPreparation( preparationTaskID, let indexTask) , . preparing( preparationTaskID, let indexTask) :
672
- inProgressIndexTasks [ fileAndTarget. file] = . updatingIndexStore(
676
+ inProgressIndexTasks [ fileAndTarget. file] ? . state = . updatingIndexStore(
673
677
updateIndexStoreTask: OpaqueQueuedIndexTask ( updateIndexTask) ,
674
678
indexTask: indexTask
675
679
)
@@ -694,34 +698,62 @@ package final actor SemanticIndexManager {
694
698
// We will check the up-to-date status again in `IndexTaskDescription.execute`. This ensures that if we schedule
695
699
// schedule two indexing jobs for the same file in quick succession, only the first one actually updates the index
696
700
// store and the second one will be a no-op once it runs.
697
- let outOfDateFiles = await filesToIndex ( toCover: files, indexFilesWithUpToDateUnits: indexFilesWithUpToDateUnit)
701
+ var filesToIndex = await filesToIndex ( toCover: files, indexFilesWithUpToDateUnits: indexFilesWithUpToDateUnit)
698
702
// sort files to get deterministic indexing order
699
- . sorted ( by: { $0. sourceFile. stringValue < $1. sourceFile. stringValue } )
700
-
701
- if outOfDateFiles. isEmpty {
702
- // Early exit if there are no files to index.
703
- return Task { }
704
- }
705
-
706
- logger. debug ( " Scheduling indexing of \( outOfDateFiles. map ( \. sourceFile. stringValue) . joined ( separator: " , " ) ) " )
707
-
708
- // Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us
709
- // to index the low-level targets ASAP.
710
- var filesByTarget : [ BuildTargetIdentifier : [ FileToIndex ] ] = [ : ]
703
+ . sorted ( by: { $0. file. sourceFile. stringValue < $1. file. sourceFile. stringValue } )
711
704
712
705
// The number of index tasks that don't currently have an in-progress task associated with it.
713
706
// The denominator in the index progress should get incremented by this amount.
714
707
// We don't want to increment the denominator for tasks that already have an index in progress.
715
708
var newIndexTasks = 0
716
- for file in outOfDateFiles {
717
- if inProgressIndexTasks [ file] == nil {
709
+ var alreadyScheduledTasks : Set < FileToIndex > = [ ]
710
+ for file in filesToIndex {
711
+ let inProgress = inProgressIndexTasks [ file. file]
712
+
713
+ let shouldScheduleIndexing : Bool
714
+ switch inProgress? . state {
715
+ case nil :
718
716
newIndexTasks += 1
717
+ shouldScheduleIndexing = true
718
+ case . creatingIndexTask, . waitingForPreparation:
719
+ // We already have a task that indexes the file but hasn't started preparation yet. Indexing the file again
720
+ // won't produce any new results.
721
+ alreadyScheduledTasks. insert ( file. file)
722
+ shouldScheduleIndexing = false
723
+ case . preparing( _, _) , . updatingIndexStore( _, _) :
724
+ // We have started indexing of the file and are now requesting to index it again. Unless we know that the file
725
+ // hasn't been modified since the last request for indexing, we need to schedule it to get re-indexed again.
726
+ if let modDate = file. fileModificationDate, inProgress? . fileModificationDate == modDate {
727
+ shouldScheduleIndexing = false
728
+ } else {
729
+ shouldScheduleIndexing = true
730
+ }
731
+ }
732
+ if shouldScheduleIndexing {
733
+ inProgressIndexTasks [ file. file] = InProgressIndexStore (
734
+ state: . creatingIndexTask,
735
+ fileModificationDate: file. fileModificationDate
736
+ )
737
+ } else {
738
+ alreadyScheduledTasks. insert ( file. file)
739
+
719
740
}
720
- inProgressIndexTasks [ file] = . creatingIndexTask
721
741
}
722
742
indexTasksWereScheduled ( newIndexTasks)
743
+ filesToIndex = filesToIndex. filter { !alreadyScheduledTasks. contains ( $0. file) }
744
+
745
+ if filesToIndex. isEmpty {
746
+ // Early exit if there are no files to index.
747
+ return Task { }
748
+ }
749
+
750
+ logger. debug ( " Scheduling indexing of \( filesToIndex. map ( \. file. sourceFile. stringValue) . joined ( separator: " , " ) ) " )
751
+
752
+ // Sort the targets in topological order so that low-level targets get built before high-level targets, allowing us
753
+ // to index the low-level targets ASAP.
754
+ var filesByTarget : [ BuildTargetIdentifier : [ ( FileToIndex ) ] ] = [ : ]
723
755
724
- for fileToIndex in outOfDateFiles {
756
+ for fileToIndex in filesToIndex . map ( \ . file ) {
725
757
guard let target = await buildSystemManager. canonicalTarget ( for: fileToIndex. mainFile) else {
726
758
logger. error (
727
759
" Not indexing \( fileToIndex. forLogging) because the target could not be determined "
@@ -768,9 +800,9 @@ package final actor SemanticIndexManager {
768
800
if case . executing = newState {
769
801
for file in filesToIndex {
770
802
if case . waitingForPreparation( preparationTaskID: preparationTaskID, indexTask: let indexTask) =
771
- self . inProgressIndexTasks [ file]
803
+ self . inProgressIndexTasks [ file] ? . state
772
804
{
773
- self . inProgressIndexTasks [ file] = . preparing(
805
+ self . inProgressIndexTasks [ file] ? . state = . preparing(
774
806
preparationTaskID: preparationTaskID,
775
807
indexTask: indexTask
776
808
)
@@ -807,7 +839,7 @@ package final actor SemanticIndexManager {
807
839
// `.waitingForPreparation` here because we don't have an `await` call between the creation of `indexTask` and
808
840
// this loop, so we still have exclusive access to the `SemanticIndexManager` actor and hence `updateIndexStore`
809
841
// can't execute until we have set all index statuses to `.waitingForPreparation`.
810
- inProgressIndexTasks [ file] = . waitingForPreparation(
842
+ inProgressIndexTasks [ file] ? . state = . waitingForPreparation(
811
843
preparationTaskID: preparationTaskID,
812
844
indexTask: indexTask
813
845
)
0 commit comments