@@ -19,8 +19,22 @@ import SKCore
19
19
private enum FileIndexStatus {
20
20
/// The index is up-to-date.
21
21
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
+ }
24
38
}
25
39
26
40
/// Schedules index tasks and keeps track of the index status of files.
@@ -46,22 +60,51 @@ public final actor SemanticIndexManager {
46
60
/// workspaces.
47
61
private let indexTaskScheduler : TaskScheduler < AnyIndexTaskDescription >
48
62
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
+
49
68
/// Callback that is called when an index task has finished.
50
69
///
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
53
75
54
76
// MARK: - Public API
55
77
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
+
56
97
public init (
57
98
index: UncheckedIndex ,
58
99
buildSystemManager: BuildSystemManager ,
59
100
indexTaskScheduler: TaskScheduler < AnyIndexTaskDescription > ,
60
- indexTaskDidFinish: ( @Sendable ( ) -> Void ) ?
101
+ indexTasksWereScheduled: @escaping @Sendable ( Int ) -> Void ,
102
+ indexTaskDidFinish: @escaping @Sendable ( ) -> Void
61
103
) {
62
104
self . index = index. checked ( for: . modifiedFiles)
63
105
self . buildSystemManager = buildSystemManager
64
106
self . indexTaskScheduler = indexTaskScheduler
107
+ self . indexTasksWereScheduled = indexTasksWereScheduled
65
108
self . indexTaskDidFinish = indexTaskDidFinish
66
109
}
67
110
@@ -93,7 +136,7 @@ public final actor SemanticIndexManager {
93
136
await withTaskGroup ( of: Void . self) { taskGroup in
94
137
for (_, status) in indexStatus {
95
138
switch status {
96
- case . inProgress ( let task) :
139
+ case . scheduled ( let task ) , . executing ( let task) :
97
140
taskGroup. addTask {
98
141
await task. value
99
142
}
@@ -138,7 +181,7 @@ public final actor SemanticIndexManager {
138
181
)
139
182
)
140
183
await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) . value
141
- self . indexTaskDidFinish ? ( )
184
+ self . indexTaskDidFinish ( )
142
185
}
143
186
144
187
/// Update the index store for the given files, assuming that their targets have already been prepared.
@@ -150,11 +193,44 @@ public final actor SemanticIndexManager {
150
193
index: self . index
151
194
)
152
195
)
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
+ }
156
232
}
157
- self . indexTaskDidFinish ? ( )
233
+ await updateIndexStoreTask . value
158
234
}
159
235
160
236
/// Index the given set of files at the given priority.
@@ -226,9 +302,15 @@ public final actor SemanticIndexManager {
226
302
}
227
303
indexTasks. append ( indexTask)
228
304
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)
231
312
}
313
+ indexTasksWereScheduled ( filesToIndex. count)
232
314
}
233
315
let indexTasksImmutable = indexTasks
234
316
0 commit comments