Skip to content

Commit d1dd4a5

Browse files
authored
Merge pull request #2270 from ahoppen/topological-target-sort
Fix issue that caused targets to be sorted top-down instead of bottom-up for background indexing
2 parents db910cf + d9046e4 commit d1dd4a5

File tree

3 files changed

+83
-20
lines changed

3 files changed

+83
-20
lines changed

Sources/BuildServerIntegration/BuildServerManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1119,7 +1119,7 @@ package actor BuildServerManager: QueueBasedMessageHandler {
11191119
let lhsDepth = buildTargets[lhs]?.depth ?? 0
11201120
let rhsDepth = buildTargets[rhs]?.depth ?? 0
11211121
if lhsDepth != rhsDepth {
1122-
return rhsDepth > lhsDepth
1122+
return lhsDepth > rhsDepth
11231123
}
11241124
return lhs.uri.stringValue < rhs.uri.stringValue
11251125
}

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -630,13 +630,12 @@ package final actor SemanticIndexManager {
630630

631631
// MARK: - Helper functions
632632

633-
private func prepare(
633+
private func schedulePreparation(
634634
targets: [BuildTargetIdentifier],
635635
purpose: TargetPreparationPurpose,
636636
priority: TaskPriority?,
637-
executionStatusChangedCallback: @escaping (QueuedTask<AnyIndexTaskDescription>, TaskExecutionState) async -> Void =
638-
{ _, _ in }
639-
) async {
637+
executionStatusChangedCallback: @escaping (QueuedTask<AnyIndexTaskDescription>, TaskExecutionState) async -> Void
638+
) async -> QueuedTask<AnyIndexTaskDescription>? {
640639
// Perform a quick initial check whether the target is up-to-date, in which case we don't need to schedule a
641640
// preparation operation at all.
642641
// We will check the up-to-date status again in `PreparationTaskDescription.execute`. This ensures that if we
@@ -647,7 +646,7 @@ package final actor SemanticIndexManager {
647646
}
648647

649648
guard !targetsToPrepare.isEmpty else {
650-
return
649+
return nil
651650
}
652651

653652
let taskDescription = AnyIndexTaskDescription(
@@ -660,7 +659,7 @@ package final actor SemanticIndexManager {
660659
)
661660
)
662661
if Task.isCancelled {
663-
return
662+
return nil
664663
}
665664
let preparationTask = await indexTaskScheduler.schedule(priority: priority, taskDescription) { task, newState in
666665
await executionStatusChangedCallback(task, newState)
@@ -689,6 +688,25 @@ package final actor SemanticIndexManager {
689688
purpose: mergedPurpose
690689
)
691690
}
691+
return preparationTask
692+
}
693+
694+
private func prepare(
695+
targets: [BuildTargetIdentifier],
696+
purpose: TargetPreparationPurpose,
697+
priority: TaskPriority?,
698+
executionStatusChangedCallback: @escaping (QueuedTask<AnyIndexTaskDescription>, TaskExecutionState) async -> Void =
699+
{ _, _ in }
700+
) async {
701+
let preparationTask = await schedulePreparation(
702+
targets: targets,
703+
purpose: purpose,
704+
priority: priority,
705+
executionStatusChangedCallback: executionStatusChangedCallback
706+
)
707+
guard let preparationTask else {
708+
return
709+
}
692710
await withTaskCancellationHandler {
693711
return await preparationTask.waitToFinish()
694712
} onCancel: {
@@ -884,22 +902,30 @@ package final actor SemanticIndexManager {
884902
let preparationTaskID = UUID()
885903
let filesToIndex = targetsBatch.flatMap({ filesByTarget[$0]! })
886904

887-
let indexTask = Task(priority: priority) {
888-
// First prepare the targets.
889-
await prepare(targets: targetsBatch, purpose: .forIndexing, priority: priority) { task, newState in
890-
if case .executing = newState {
891-
for file in filesToIndex {
892-
if case .waitingForPreparation(preparationTaskID: preparationTaskID, indexTask: let indexTask) =
893-
self.inProgressIndexTasks[file]?.state
894-
{
895-
self.inProgressIndexTasks[file]?.state = .preparing(
896-
preparationTaskID: preparationTaskID,
897-
indexTask: indexTask
898-
)
899-
}
905+
// First schedule preparation of the targets. We schedule the preparation outside of `indexTask` so that we
906+
// deterministically prepare targets in the topological order for indexing. If we triggered preparation inside the
907+
// indexTask, we would get nondeterministic ordering since Tasks may start executing in any order.
908+
let preparationTask = await schedulePreparation(
909+
targets: targetsBatch,
910+
purpose: .forIndexing,
911+
priority: priority
912+
) { task, newState in
913+
if case .executing = newState {
914+
for file in filesToIndex {
915+
if case .waitingForPreparation(preparationTaskID: preparationTaskID, indexTask: let indexTask) =
916+
self.inProgressIndexTasks[file]?.state
917+
{
918+
self.inProgressIndexTasks[file]?.state = .preparing(
919+
preparationTaskID: preparationTaskID,
920+
indexTask: indexTask
921+
)
900922
}
901923
}
902924
}
925+
}
926+
927+
let indexTask = Task(priority: priority) {
928+
await preparationTask?.waitToFinishPropagatingCancellation()
903929

904930
// And after preparation is done, index the files in the targets.
905931
await withTaskGroup(of: Void.self) { taskGroup in

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2629,6 +2629,43 @@ final class BackgroundIndexingTests: XCTestCase {
26292629
let symbols = try await project.testClient.send(WorkspaceSymbolsRequest(query: "myTestFu"))
26302630
XCTAssertEqual(symbols?.count, 1)
26312631
}
2632+
2633+
func testTargetsAreIndexedInDependencyOrder() async throws {
2634+
// We want to prepare low-level targets before high-level targets to make progress on indexing more quickly.
2635+
let preparationRequests = ThreadSafeBox<[BuildTargetPrepareRequest]>(initialValue: [])
2636+
let testHooks = Hooks(
2637+
buildServerHooks: BuildServerHooks(preHandleRequest: { request in
2638+
if let request = request as? BuildTargetPrepareRequest {
2639+
preparationRequests.value.append(request)
2640+
}
2641+
})
2642+
)
2643+
_ = try await SwiftPMTestProject(
2644+
files: [
2645+
"LibA/LibA.swift": "",
2646+
"LibB/LibB.swift": "",
2647+
],
2648+
manifest: """
2649+
let package = Package(
2650+
name: "MyLibrary",
2651+
targets: [
2652+
.target(name: "LibA"),
2653+
.target(name: "LibB", dependencies: ["LibA"])
2654+
]
2655+
)
2656+
""",
2657+
hooks: testHooks,
2658+
enableBackgroundIndexing: true,
2659+
pollIndex: true
2660+
)
2661+
XCTAssertEqual(
2662+
preparationRequests.value.flatMap(\.targets),
2663+
[
2664+
try BuildTargetIdentifier(target: "LibA", destination: .target),
2665+
try BuildTargetIdentifier(target: "LibB", destination: .target),
2666+
]
2667+
)
2668+
}
26322669
}
26332670

26342671
extension HoverResponseContents {

0 commit comments

Comments
 (0)