Skip to content

Commit 4f0c40a

Browse files
authored
Merge pull request #542 from swiftlang/owenv/single-use-backtraces
Improve task backtraces for dynamic tasks
2 parents b572273 + e723b35 commit 4f0c40a

17 files changed

+263
-110
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ let package = Package(
369369
// Perf tests
370370
.testTarget(
371371
name: "SWBBuildSystemPerfTests",
372-
dependencies: ["SWBBuildSystem", "SWBTestSupport"],
372+
dependencies: ["SWBBuildSystem", "SWBTestSupport", "SwiftBuildTestSupport"],
373373
swiftSettings: swiftSettings(languageMode: .v6)),
374374
.testTarget(
375375
name: "SWBCASPerfTests",

Sources/SWBProtocol/BuildOperationMessages.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public struct BuildOperationTargetInfo: SerializableCodable, Equatable, Sendable
126126
}
127127
}
128128

129-
public enum BuildOperationTaskSignature: RawRepresentable, Sendable, Hashable, Codable, CustomDebugStringConvertible {
129+
public enum BuildOperationTaskSignature: RawRepresentable, Sendable, Comparable, Hashable, Codable, CustomDebugStringConvertible {
130130
case taskIdentifier(ByteString)
131131
case activitySignature(ByteString)
132132
case subtaskSignature(ByteString)
@@ -155,6 +155,10 @@ public enum BuildOperationTaskSignature: RawRepresentable, Sendable, Hashable, C
155155
}
156156
}
157157

158+
public static func < (lhs: BuildOperationTaskSignature, rhs: BuildOperationTaskSignature) -> Bool {
159+
lhs.rawValue.lexicographicallyPrecedes(rhs.rawValue)
160+
}
161+
158162
public init(from decoder: any Decoder) throws {
159163
let container = try decoder.singleValueContainer()
160164
guard let value = BuildOperationTaskSignature(rawValue: ByteString(try container.decode([UInt8].self))) else {
@@ -1020,7 +1024,7 @@ public struct BuildOperationDiagnosticEmitted: Message, Equatable, SerializableC
10201024
}
10211025
}
10221026

1023-
public struct BuildOperationBacktraceFrameEmitted: Message, Equatable, SerializableCodable {
1027+
public struct BuildOperationBacktraceFrameEmitted: Message, Equatable, Hashable, SerializableCodable {
10241028
public static let name = "BUILD_BACKTRACE_FRAME_EMITTED"
10251029

10261030
public enum Identifier: Hashable, Equatable, Comparable, SerializableCodable, Sendable {

Sources/SWBTestSupport/BuildOperationTester.swift

Lines changed: 4 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ package final class BuildOperationTester {
155155
case subtaskDidReportProgress(SubtaskProgressEvent, count: Int)
156156

157157
/// The build emitted a backtrace frame.
158-
case emittedBuildBacktraceFrame(identifier: SWBProtocol.BuildOperationBacktraceFrameEmitted.Identifier, previousFrameIdentifier: SWBProtocol.BuildOperationBacktraceFrameEmitted.Identifier?, category: SWBProtocol.BuildOperationBacktraceFrameEmitted.Category, description: String)
158+
case emittedBuildBacktraceFrame(BuildOperationBacktraceFrameEmitted)
159159

160160
package var description: String {
161161
switch self {
@@ -189,8 +189,8 @@ package final class BuildOperationTester {
189189
return "activityEmittedData(\(ruleInfo), bytes: \(ByteString(bytes).asString)"
190190
case .activityEnded(ruleInfo: let ruleInfo):
191191
return "activityEnded(\(ruleInfo))"
192-
case .emittedBuildBacktraceFrame(identifier: let id, previousFrameIdentifier: let previousID, category: let category, description: let description):
193-
return "emittedBuildBacktraceFrame(\(id), previous: \(String(describing: previousID)), category: \(category), description: \(description))"
192+
case .emittedBuildBacktraceFrame(let frame):
193+
return "emittedBuildBacktraceFrame(\(frame.identifier), previous: \(String(describing: frame.previousFrameIdentifier)), category: \(frame.category), description: \(frame.description))"
194194
case .previouslyBatchedSubtaskUpToDate(let signature):
195195
return "previouslyBatchedSubtaskUpToDate(\(signature))"
196196
}
@@ -735,18 +735,6 @@ package final class BuildOperationTester {
735735

736736
}
737737

738-
package func checkNoTaskWithBacktraces(_ conditions: TaskCondition..., sourceLocation: SourceLocation = #_sourceLocation) {
739-
for matchedTask in findMatchingTasks(conditions) {
740-
Issue.record("found unexpected task matching conditions '\(conditions)', found: \(matchedTask)", sourceLocation: sourceLocation)
741-
742-
if let frameID = getBacktraceID(matchedTask, sourceLocation: sourceLocation) {
743-
enumerateBacktraces(frameID) { _, category, description in
744-
Issue.record("...<category='\(category)' description='\(description)'>", sourceLocation: sourceLocation)
745-
}
746-
}
747-
}
748-
}
749-
750738
/// Check whether the results contains a dependency cycle error. If so, then consume the error and create a `CycleChecking` object and pass it to the block. Otherwise fail.
751739
package func checkDependencyCycle(_ pattern: StringPattern, kind: DiagnosticKind = .error, failIfNotFound: Bool = true, sourceLocation: SourceLocation = #_sourceLocation, body: (CycleChecker) async throws -> Void) async throws {
752740
guard let message = getDiagnosticMessage(pattern, kind: kind, checkDiagnostic: { _ in true }) else {
@@ -1045,55 +1033,6 @@ package final class BuildOperationTester {
10451033
startedTasks.remove(task)
10461034
}
10471035

1048-
private func getBacktraceID(_ task: Task, sourceLocation: SourceLocation = #_sourceLocation) -> BuildOperationBacktraceFrameEmitted.Identifier? {
1049-
guard let frameID: BuildOperationBacktraceFrameEmitted.Identifier = events.compactMap ({ (event) -> BuildOperationBacktraceFrameEmitted.Identifier? in
1050-
guard case .emittedBuildBacktraceFrame(identifier: let identifier, previousFrameIdentifier: _, category: _, description: _) = event, case .task(let signature) = identifier, BuildOperationTaskSignature.taskIdentifier(ByteString(encodingAsUTF8: task.identifier.rawValue)) == signature else {
1051-
return nil
1052-
}
1053-
return identifier
1054-
// Iff the task is a dynamic task, there may be more than one corresponding frame if it was requested multiple times, in which case we choose the first. Non-dynamic tasks always have a 1-1 relationship with frames.
1055-
}).sorted().first else {
1056-
Issue.record("Did not find a single build backtrace frame for task: \(task.identifier)", sourceLocation: sourceLocation)
1057-
return nil
1058-
}
1059-
return frameID
1060-
}
1061-
1062-
private func enumerateBacktraces(_ identifier: BuildOperationBacktraceFrameEmitted.Identifier, _ handleFrameInfo: (_ identifier: BuildOperationBacktraceFrameEmitted.Identifier?, _ category: BuildOperationBacktraceFrameEmitted.Category, _ description: String) -> ()) {
1063-
var currentFrameID: BuildOperationBacktraceFrameEmitted.Identifier? = identifier
1064-
while let id = currentFrameID {
1065-
if let frameInfo: (BuildOperationBacktraceFrameEmitted.Identifier?, BuildOperationBacktraceFrameEmitted.Category, String) = events.compactMap({ (event) -> (BuildOperationBacktraceFrameEmitted.Identifier?, BuildOperationBacktraceFrameEmitted.Category, String)? in
1066-
guard case .emittedBuildBacktraceFrame(identifier: id, previousFrameIdentifier: let previousFrameIdentifier, category: let category, description: let description) = event else {
1067-
return nil
1068-
}
1069-
return (previousFrameIdentifier, category, description)
1070-
// Iff the task is a dynamic task, there may be more than one corresponding frame if it was requested multiple times, in which case we choose the first. Non-dynamic tasks always have a 1-1 relationship with frames.
1071-
}).sorted(by: { $0.0 }).first {
1072-
handleFrameInfo(frameInfo.0, frameInfo.1, frameInfo.2)
1073-
currentFrameID = frameInfo.0
1074-
} else {
1075-
currentFrameID = nil
1076-
}
1077-
}
1078-
}
1079-
1080-
package func checkBacktrace(_ identifier: BuildOperationBacktraceFrameEmitted.Identifier, _ patterns: [StringPattern], sourceLocation: SourceLocation = #_sourceLocation) {
1081-
var frameDescriptions: [String] = []
1082-
enumerateBacktraces(identifier) { (_, category, description) in
1083-
frameDescriptions.append("<category='\(category)' description='\(description)'>")
1084-
}
1085-
1086-
XCTAssertMatch(frameDescriptions, patterns, sourceLocation: sourceLocation)
1087-
}
1088-
1089-
package func checkBacktrace(_ task: Task, _ patterns: [StringPattern], sourceLocation: SourceLocation = #_sourceLocation) {
1090-
if let frameID = getBacktraceID(task, sourceLocation: sourceLocation) {
1091-
checkBacktrace(frameID, patterns, sourceLocation: sourceLocation)
1092-
} else {
1093-
// already recorded an issue
1094-
}
1095-
}
1096-
10971036
private class TaskDependencyResolver {
10981037
/// The database schema has to match what `BuildSystemImpl` defines in `getMergedSchemaVersion()`.
10991038
/// Can be removed once rdar://85336712 is resolved.
@@ -1563,42 +1502,6 @@ package final class BuildOperationTester {
15631502
}
15641503
}
15651504

1566-
/// Ensure that the build is a null build.
1567-
package func checkNullBuild(_ name: String? = nil, parameters: BuildParameters? = nil, runDestination: RunDestinationInfo?, buildRequest inputBuildRequest: BuildRequest? = nil, buildCommand: BuildCommand? = nil, schemeCommand: SchemeCommand? = .launch, persistent: Bool = false, serial: Bool = false, buildOutputMap: [String:String]? = nil, signableTargets: Set<String> = [], signableTargetInputs: [String: ProvisioningTaskInputs] = [:], clientDelegate: (any ClientDelegate)? = nil, excludedTasks: Set<String> = ["ClangStatCache", "LinkAssetCatalogSignature"], diagnosticsToValidate: Set<DiagnosticKind> = [.note, .error, .warning], sourceLocation: SourceLocation = #_sourceLocation) async throws {
1568-
1569-
func body(results: BuildResults) throws -> Void {
1570-
results.consumeTasksMatchingRuleTypes(excludedTasks)
1571-
results.checkNoTaskWithBacktraces(sourceLocation: sourceLocation)
1572-
1573-
results.checkNote(.equal("Building targets in dependency order"), failIfNotFound: false)
1574-
results.checkNote(.prefix("Target dependency graph"), failIfNotFound: false)
1575-
1576-
for kind in diagnosticsToValidate {
1577-
switch kind {
1578-
case .note:
1579-
results.checkNoNotes(sourceLocation: sourceLocation)
1580-
1581-
case .warning:
1582-
results.checkNoWarnings(sourceLocation: sourceLocation)
1583-
1584-
case .error:
1585-
results.checkNoErrors(sourceLocation: sourceLocation)
1586-
1587-
case .remark:
1588-
results.checkNoRemarks(sourceLocation: sourceLocation)
1589-
1590-
default:
1591-
// other kinds are ignored
1592-
break
1593-
}
1594-
}
1595-
}
1596-
1597-
try await UserDefaults.withEnvironment(["EnableBuildBacktraceRecording": "true"]) {
1598-
try await checkBuild(name, parameters: parameters, runDestination: runDestination, buildRequest: inputBuildRequest, buildCommand: buildCommand, schemeCommand: schemeCommand, persistent: persistent, serial: serial, buildOutputMap: buildOutputMap, signableTargets: signableTargets, signableTargetInputs: signableTargetInputs, clientDelegate: clientDelegate, sourceLocation: sourceLocation, body: body)
1599-
}
1600-
}
1601-
16021505
package static func buildRequestForIndexOperation(
16031506
workspace: Workspace,
16041507
buildTargets: [any TestTarget]? = nil,
@@ -2252,7 +2155,7 @@ private final class BuildOperationTesterDelegate: BuildOperationDelegate {
22522155

22532156
func recordBuildBacktraceFrame(identifier: SWBProtocol.BuildOperationBacktraceFrameEmitted.Identifier, previousFrameIdentifier: SWBProtocol.BuildOperationBacktraceFrameEmitted.Identifier?, category: SWBProtocol.BuildOperationBacktraceFrameEmitted.Category, kind: SWBProtocol.BuildOperationBacktraceFrameEmitted.Kind, description: String) {
22542157
queue.async {
2255-
self.events.append(.emittedBuildBacktraceFrame(identifier: identifier, previousFrameIdentifier: previousFrameIdentifier, category: category, description: description))
2158+
self.events.append(.emittedBuildBacktraceFrame(.init(identifier: identifier, previousFrameIdentifier: previousFrameIdentifier, category: category, kind: kind, description: description)))
22562159
}
22572160
}
22582161
}

Sources/SwiftBuild/SWBBuildOperationBacktraceFrame.swift

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import SWBUtil
1515

1616
public import Foundation
1717

18-
public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Identifiable {
19-
public struct Identifier: Equatable, Hashable, Sendable, Codable, CustomDebugStringConvertible {
20-
private enum Storage: Equatable, Hashable, Sendable, Codable {
18+
public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Identifiable, Comparable {
19+
public struct Identifier: Equatable, Comparable, Hashable, Sendable, Codable, CustomDebugStringConvertible {
20+
private enum Storage: Equatable, Comparable, Hashable, Sendable, Codable {
2121
case task(BuildOperationTaskSignature)
2222
case key(String)
2323
}
@@ -39,6 +39,10 @@ public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Iden
3939
self.storage = .task(taskSignature)
4040
}
4141

42+
package init(genericBuildKey: String) {
43+
self.storage = .key(genericBuildKey)
44+
}
45+
4246
public var debugDescription: String {
4347
switch storage {
4448
case .task(let taskSignature):
@@ -47,9 +51,13 @@ public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Iden
4751
return key
4852
}
4953
}
54+
55+
public static func < (lhs: SWBBuildOperationBacktraceFrame.Identifier, rhs: SWBBuildOperationBacktraceFrame.Identifier) -> Bool {
56+
lhs.storage < rhs.storage
57+
}
5058
}
5159

52-
public enum Category: Equatable, Hashable, Sendable, Codable {
60+
public enum Category: Equatable, Comparable, Hashable, Sendable, Codable {
5361
case ruleNeverBuilt
5462
case ruleSignatureChanged
5563
case ruleHadInvalidValue
@@ -68,7 +76,7 @@ public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Iden
6876
}
6977
}
7078
}
71-
public enum Kind: Equatable, Hashable, Sendable, Codable {
79+
public enum Kind: Equatable, Comparable, Hashable, Sendable, Codable {
7280
case genericTask
7381
case swiftDriverJob
7482
case file
@@ -82,6 +90,14 @@ public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Iden
8290
public let description: String
8391
public let frameKind: Kind
8492

93+
package init(identifier: Identifier, previousFrameIdentifier: Identifier?, category: Category, description: String, frameKind: Kind) {
94+
self.identifier = identifier
95+
self.previousFrameIdentifier = previousFrameIdentifier
96+
self.category = category
97+
self.description = description
98+
self.frameKind = frameKind
99+
}
100+
85101
// The old name collides with the `kind` key used in the SwiftBuildMessage JSON encoding
86102
@available(*, deprecated, renamed: "frameKind")
87103
public var kind: Kind {
@@ -91,6 +107,10 @@ public struct SWBBuildOperationBacktraceFrame: Hashable, Sendable, Codable, Iden
91107
public var id: Identifier {
92108
identifier
93109
}
110+
111+
public static func < (lhs: SWBBuildOperationBacktraceFrame, rhs: SWBBuildOperationBacktraceFrame) -> Bool {
112+
(lhs.identifier, lhs.previousFrameIdentifier, lhs.category, lhs.description, lhs.frameKind) < (rhs.identifier, rhs.previousFrameIdentifier, rhs.category, rhs.description, rhs.frameKind)
113+
}
94114
}
95115

96116
extension SWBBuildOperationBacktraceFrame {
@@ -134,3 +154,43 @@ extension SWBBuildOperationBacktraceFrame {
134154
self.init(identifier: id, previousFrameIdentifier: previousID, category: category, description: message.description, frameKind: kind)
135155
}
136156
}
157+
158+
public struct SWBBuildOperationCollectedBacktraceFrames {
159+
fileprivate var frames: [SWBBuildOperationBacktraceFrame.Identifier: Set<SWBBuildOperationBacktraceFrame>]
160+
161+
public init() {
162+
self.frames = [:]
163+
}
164+
165+
public mutating func add(frame: SWBBuildOperationBacktraceFrame) {
166+
frames[frame.identifier, default: []].insert(frame)
167+
}
168+
}
169+
170+
public struct SWBTaskBacktrace {
171+
public let frames: [SWBBuildOperationBacktraceFrame]
172+
173+
public init?(from baseFrameID: SWBBuildOperationBacktraceFrame.Identifier, collectedFrames: SWBBuildOperationCollectedBacktraceFrames) {
174+
var frames: [SWBBuildOperationBacktraceFrame] = []
175+
var currentFrame = collectedFrames.frames[baseFrameID]?.only
176+
while let frame = currentFrame {
177+
frames.append(frame)
178+
if let previousFrameID = frame.previousFrameIdentifier, let candidatesForNextFrame = collectedFrames.frames[previousFrameID] {
179+
switch frame.category {
180+
case .dynamicTaskRegistration:
181+
currentFrame = candidatesForNextFrame.sorted().first {
182+
$0.category == .dynamicTaskRequest
183+
}
184+
default:
185+
currentFrame = candidatesForNextFrame.sorted().first
186+
}
187+
} else {
188+
currentFrame = nil
189+
}
190+
}
191+
guard !frames.isEmpty else {
192+
return nil
193+
}
194+
self.frames = frames
195+
}
196+
}

0 commit comments

Comments
 (0)