@@ -19,8 +19,22 @@ import SKCore
1919private enum FileIndexStatus {
2020 /// The index is up-to-date.
2121 case upToDate
22- /// The file is being indexed by the given task.
23- case inProgress( Task < Void , Never > )
22+ /// The file is not up to date and we have scheduled a task to index it but that index operation hasn't been started
23+ /// yet.
24+ case scheduled( Task < Void , Never > )
25+ /// We are currently actively indexing this file, ie. we are running a subprocess that indexes the file.
26+ case executing( Task < Void , Never > )
27+
28+ var description : String {
29+ switch self {
30+ case . upToDate:
31+ return " upToDate "
32+ case . scheduled:
33+ return " scheduled "
34+ case . executing:
35+ return " executing "
36+ }
37+ }
2438}
2539
2640/// Schedules index tasks and keeps track of the index status of files.
@@ -46,22 +60,51 @@ public final actor SemanticIndexManager {
4660 /// workspaces.
4761 private let indexTaskScheduler : TaskScheduler < AnyIndexTaskDescription >
4862
63+ /// Called when files are scheduled to be indexed.
64+ ///
65+ /// The parameter is the number of files that were scheduled to be indexed.
66+ private let indexTasksWereScheduled : @Sendable ( _ numberOfFileScheduled: Int ) -> Void
67+
4968 /// Callback that is called when an index task has finished.
5069 ///
51- /// Currently only used for testing.
52- private let indexTaskDidFinish : ( @Sendable ( ) -> Void ) ?
70+ /// An object observing this property probably wants to check `inProgressIndexTasks` when the callback is called to
71+ /// get the current list of in-progress index tasks.
72+ ///
73+ /// The number of `indexTaskDidFinish` calls does not have to relate to the number of `indexTasksWereScheduled` calls.
74+ private let indexTaskDidFinish : @Sendable ( ) -> Void
5375
5476 // MARK: - Public API
5577
78+ /// The files that still need to be indexed.
79+ ///
80+ /// See `FileIndexStatus` for the distinction between `scheduled` and `executing`.
81+ public var inProgressIndexTasks : ( scheduled: [ DocumentURI ] , executing: [ DocumentURI ] ) {
82+ let scheduled = indexStatus. compactMap { ( uri: DocumentURI , status: FileIndexStatus ) in
83+ if case . scheduled = status {
84+ return uri
85+ }
86+ return nil
87+ }
88+ let inProgress = indexStatus. compactMap { ( uri: DocumentURI , status: FileIndexStatus ) in
89+ if case . executing = status {
90+ return uri
91+ }
92+ return nil
93+ }
94+ return ( scheduled, inProgress)
95+ }
96+
5697 public init (
5798 index: UncheckedIndex ,
5899 buildSystemManager: BuildSystemManager ,
59100 indexTaskScheduler: TaskScheduler < AnyIndexTaskDescription > ,
60- indexTaskDidFinish: ( @Sendable ( ) -> Void ) ?
101+ indexTasksWereScheduled: @escaping @Sendable ( Int ) -> Void ,
102+ indexTaskDidFinish: @escaping @Sendable ( ) -> Void
61103 ) {
62104 self . index = index. checked ( for: . modifiedFiles)
63105 self . buildSystemManager = buildSystemManager
64106 self . indexTaskScheduler = indexTaskScheduler
107+ self . indexTasksWereScheduled = indexTasksWereScheduled
65108 self . indexTaskDidFinish = indexTaskDidFinish
66109 }
67110
@@ -93,7 +136,7 @@ public final actor SemanticIndexManager {
93136 await withTaskGroup ( of: Void . self) { taskGroup in
94137 for (_, status) in indexStatus {
95138 switch status {
96- case . inProgress ( let task) :
139+ case . scheduled ( let task ) , . executing ( let task) :
97140 taskGroup. addTask {
98141 await task. value
99142 }
@@ -138,7 +181,7 @@ public final actor SemanticIndexManager {
138181 )
139182 )
140183 await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) . value
141- self . indexTaskDidFinish ? ( )
184+ self . indexTaskDidFinish ( )
142185 }
143186
144187 /// Update the index store for the given files, assuming that their targets have already been prepared.
@@ -150,11 +193,44 @@ public final actor SemanticIndexManager {
150193 index: self . index. unchecked
151194 )
152195 )
153- await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) . value
154- for file in files {
155- self . indexStatus [ file] = . upToDate
196+ let updateIndexStoreTask = await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) { newState in
197+ switch newState {
198+ case . executing:
199+ for file in files {
200+ if case . scheduled( let task) = self . indexStatus [ file] {
201+ self . indexStatus [ file] = . executing( task)
202+ } else {
203+ logger. fault (
204+ """
205+ Index status of \( file) is in an unexpected state \
206+ ' \( self . indexStatus [ file] ? . description ?? " <nil> " , privacy: . public) ' when update index store task \
207+ started executing
208+ """
209+ )
210+ }
211+ }
212+ case . cancelledToBeRescheduled:
213+ for file in files {
214+ if case . executing( let task) = self . indexStatus [ file] {
215+ self . indexStatus [ file] = . scheduled( task)
216+ } else {
217+ logger. fault (
218+ """
219+ Index status of \( file) is in an unexpected state \
220+ ' \( self . indexStatus [ file] ? . description ?? " <nil> " , privacy: . public) ' when update index store task \
221+ is cancelled to be rescheduled.
222+ """
223+ )
224+ }
225+ }
226+ case . finished:
227+ for file in files {
228+ self . indexStatus [ file] = . upToDate
229+ }
230+ self . indexTaskDidFinish ( )
231+ }
156232 }
157- self . indexTaskDidFinish ? ( )
233+ await updateIndexStoreTask . value
158234 }
159235
160236 /// Index the given set of files at the given priority.
@@ -226,9 +302,15 @@ public final actor SemanticIndexManager {
226302 }
227303 indexTasks. append ( indexTask)
228304
229- for file in targetsBatch. flatMap ( { filesByTarget [ $0] ! } ) {
230- indexStatus [ file] = . inProgress( indexTask)
305+ let filesToIndex = targetsBatch. flatMap ( { filesByTarget [ $0] ! } )
306+ for file in filesToIndex {
307+ // indexStatus will get set to `.upToDate` by `updateIndexStore`. Setting it to `.upToDate` cannot race with
308+ // setting it to `.scheduled` because we don't have an `await` call between the creation of `indexTask` and
309+ // this loop, so we still have exclusive access to the `SemanticIndexManager` actor and hence `updateIndexStore`
310+ // can't execute until we have set all index statuses to `.scheduled`.
311+ indexStatus [ file] = . scheduled( indexTask)
231312 }
313+ indexTasksWereScheduled ( filesToIndex. count)
232314 }
233315 let indexTasksImmutable = indexTasks
234316
0 commit comments