@@ -18,13 +18,28 @@ import SwiftExtensions
1818
1919/// Task metadata for `SyntacticTestIndexer.indexingQueue`
2020fileprivate enum TaskMetadata : DependencyTracker , Equatable {
21- case read
21+ /// Determine the list of test files from the build system and scan them for tests. Only created when the
22+ /// `SyntacticTestIndex` is created
23+ case initialPopulation
24+
25+ /// Index the files in the given set for tests
2226 case index( Set < DocumentURI > )
2327
28+ /// Retrieve information about syntactically discovered tests from the index.
29+ case read
30+
2431 /// Reads can be concurrent and files can be indexed concurrently. But we need to wait for all files to finish
2532 /// indexing before reading the index.
2633 func isDependency( of other: TaskMetadata ) -> Bool {
2734 switch ( self , other) {
35+ case ( . initialPopulation, _) :
36+ // The initial population need to finish before we can do anything with the task.
37+ return true
38+ case ( _, . initialPopulation) :
39+ // Should never happen because the initial population should only be scheduled once before any other operations
40+ // on the test index. But be conservative in case we do get an `initialPopulation` somewhere in between and use it
41+ // as a full blocker on the queue.
42+ return true
2843 case ( . read, . read) :
2944 // We allow concurrent reads
3045 return false
@@ -109,7 +124,23 @@ actor SyntacticTestIndex {
109124 /// indexing tasks to finish.
110125 private let indexingQueue = AsyncQueue < TaskMetadata > ( )
111126
112- init ( ) { }
127+ init ( determineTestFiles: @Sendable @escaping ( ) async -> [ DocumentURI ] ) {
128+ indexingQueue. async ( priority: . low, metadata: . initialPopulation) {
129+ let testFiles = await determineTestFiles ( )
130+
131+ // Divide the files into multiple batches. This is more efficient than spawning a new task for every file, mostly
132+ // because it keeps the number of pending items in `indexingQueue` low and adding a new task to `indexingQueue` is
133+ // in O(number of pending tasks), since we need to scan for dependency edges to add, which would make scanning files
134+ // be O(number of files).
135+ // Over-subscribe the processor count in case one batch finishes more quickly than another.
136+ let batches = testFiles. partition ( intoNumberOfBatches: ProcessInfo . processInfo. processorCount * 4 )
137+ await batches. concurrentForEach { filesInBatch in
138+ for uri in filesInBatch {
139+ await self . rescanFileAssumingOnQueue ( uri)
140+ }
141+ }
142+ }
143+ }
113144
114145 private func removeFilesFromIndex( _ removedFiles: Set < DocumentURI > ) {
115146 self . removedFiles. formUnion ( removedFiles)
0 commit comments