Skip to content

[5.6] SwiftDriver: address TODO about abnormal process termination #961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,15 @@ import Glibc

buildRecordInfo?.jobFinished(job: job, result: result)

// FIXME: Currently, TSCBasic.Process uses NSProcess on Windows and discards
// the bits of the exit code used to differentiate between normal and abnormal
// termination.
#if !os(Windows)
#if os(Windows)
if case .abnormal = result.exitStatus {
anyJobHadAbnormalExit = true
}
#else
if case .signalled = result.exitStatus {
anyJobHadAbnormalExit = true
}
#endif
#endif

switch mode {
case .silent:
Expand All @@ -123,7 +124,13 @@ import Glibc
pid: pid).map {
ParsableMessage(name: job.kind.rawValue, kind: .finished($0))
}
#if !os(Windows)
#if os(Windows)
case .abnormal(let exception):
messages = constructAbnormalExitMessage(job: job, output: output,
exception: exception, pid: pid).map {
ParsableMessage(name: job.kind.rawValue, kind: .abnormal($0))
}
#else
case .signalled(let signal):
let errorMessage = strsignal(signal).map { String(cString: $0) } ?? ""
messages = constructJobSignalledMessages(job: job, error: errorMessage, output: output,
Expand All @@ -132,6 +139,7 @@ import Glibc
}
#endif
}

for message in messages {
emit(message)
}
Expand Down Expand Up @@ -265,6 +273,22 @@ private extension ToolExecutionDelegate {
return FinishedMessage(exitStatus: Int(exitCode), output: output, pid: pid, realPid: realPid)
}

// MARK: - Abnormal Exit
func constructAbnormalExitMessage(job: Job, output: String?, exception: UInt32, pid: Int) -> [AbnormalExitMessage] {
let result: [AbnormalExitMessage]
if job.kind == .compile, job.primaryInputs.count > 1 {
result = job.primaryInputs.map {
guard let quasiPid = batchJobInputQuasiPIDMap[(job, $0)] else {
fatalError("Parsable-Output batch sub-job abnormal exit with no matching started message: \(job.description): \($0.file.description)")
}
return AbnormalExitMessage(pid: quasiPid, realPid: pid, output: output, exception: exception)
}
} else {
result = [AbnormalExitMessage(pid: pid, realPid: pid, output: output, exception: exception)]
}
return result
}

// MARK: - Job Signalled
func constructJobSignalledMessages(job: Job, error: String, output: String?,
signal: Int32, pid: Int) -> [SignalledMessage] {
Expand Down
7 changes: 5 additions & 2 deletions Sources/SwiftDriver/Execution/DriverExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,13 @@ extension DriverExecutor {
switch exitStatus {
case .terminated(let code):
returnCode = Int(code)
#if !os(Windows)
#if os(Windows)
case .abnormal(let exception):
returnCode = Int(exception)
#else
case .signalled(let signal):
returnCode = Int(signal)
#endif
#endif
}
return returnCode
}
Expand Down
25 changes: 25 additions & 0 deletions Sources/SwiftDriver/Execution/ParsableOutput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Foundation
public enum Kind {
case began(BeganMessage)
case finished(FinishedMessage)
case abnormal(AbnormalExitMessage)
case signalled(SignalledMessage)
case skipped(SkippedMessage)
}
Expand Down Expand Up @@ -133,6 +134,27 @@ import Foundation
}
}

@_spi(Testing) public struct AbnormalExitMessage: Encodable {
let pid: Int
let process: ActualProcess
let output: String?
let exception: UInt32

public init(pid: Int, realPid: Int, output: String?, exception: UInt32) {
self.pid = pid
self.process = ActualProcess(realPid: realPid)
self.output = output
self.exception = exception
}

private enum CodingKeys: String, CodingKey {
case pid
case process
case output
case exception
}
}

@_spi(Testing) public struct SignalledMessage: Encodable {
let pid: Int
let process: ActualProcess
Expand Down Expand Up @@ -174,6 +196,9 @@ import Foundation
case .finished(let msg):
try container.encode("finished", forKey: .kind)
try msg.encode(to: encoder)
case .abnormal(let msg):
try container.encode("abnormal-exit", forKey: .kind)
try msg.encode(to: encoder)
case .signalled(let msg):
try container.encode("signalled", forKey: .kind)
try msg.encode(to: encoder)
Expand Down
5 changes: 4 additions & 1 deletion Sources/SwiftDriver/Jobs/PrebuiltModulesJob.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ fileprivate class ModuleCompileDelegate: JobExecutionDelegate {
stderrStream.flush()
}
}
#if !os(Windows)
#if os(Windows)
case .abnormal(let exception):
diagnosticsEngine.emit(.remark("\(job.moduleName) exception: \(exception)"))
#else
case .signalled:
diagnosticsEngine.emit(.remark("\(job.moduleName) interrupted"))
#endif
Expand Down
16 changes: 13 additions & 3 deletions Sources/SwiftDriverExecution/MultiJobExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,13 @@ public final class MultiJobExecutor {
switch (result.exitStatus, continueBuildingAfterErrors) {
case (.terminated(let code), false) where code != EXIT_SUCCESS:
isBuildCancelled = true
#if !os(Windows)
#if os(Windows)
case (.abnormal, false):
isBuildCancelled = true
#else
case (.signalled, _):
isBuildCancelled = true
#endif
#endif
default:
break
}
Expand Down Expand Up @@ -614,7 +617,10 @@ class ExecuteJobRule: LLBuildRule {
if !job.kind.isCompile || code != EXIT_FAILURE {
context.diagnosticsEngine.emit(.error_command_failed(kind: job.kind, code: code))
}
#if !os(Windows)
#if os(Windows)
case let .abnormal(exception):
context.diagnosticsEngine.emit(.error_command_exception(kind: job.kind, exception: exception))
#else
case let .signalled(signal):
// An interrupt of an individual compiler job means it was deliberatly cancelled,
// most likely by the driver itself. This does not constitute an error.
Expand Down Expand Up @@ -667,4 +673,8 @@ private extension TSCBasic.Diagnostic.Message {
static func error_command_signalled(kind: Job.Kind, signal: Int32) -> TSCBasic.Diagnostic.Message {
.error("\(kind.rawValue) command failed due to signal \(signal) (use -v to see invocation)")
}

static func error_command_exception(kind: Job.Kind, exception: UInt32) -> TSCBasic.Diagnostic.Message {
.error("\(kind.rawValue) command failed due to exception \(exception) (use -v to see invocation)")
}
}
19 changes: 19 additions & 0 deletions Tests/SwiftDriverTests/ParsableMessageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ final class ParsableMessageTests: XCTestCase {
""")
}

func testAbnormalExitMessage() throws {
let exit = AbnormalExitMessage(pid: 1024, realPid: 1024, output: nil, exception: 0x8000_0003)
let message = ParsableMessage(name: "compile", kind: .abnormal(exit))
let encoded = try message.toJSON()
let string = String(data: encoded, encoding: .utf8)!

XCTAssertEqual(string, """
{
"exception" : 2147483651,
"kind" : "abnormal-exit",
"name" : "compile",
"pid" : 1024,
"process" : {
"real_pid" : 1024
}
}
""")
}

func testBeganBatchMessages() throws {
do {
try withTemporaryDirectory { path in
Expand Down