Skip to content

Commit 8fdb554

Browse files
authored
Introduce process file descriptor (pidfd) based process monitoring for Linux (#125)
* Introduce process file descriptor (pidfd) based process monitoring for Linux * Use signal handler for process state monitoring on Linux kernel lower than 5.4 * Disable testPlatformOptionsRunAsUser on CI if we don't have privilege to create a temporary user * Remove unused consoleBehavior from Execution * Add PlatformConformance in tests to ensure platform specifc types follow a uniform shape * Disable testPlatformOptionsRunAsUser until we can resolve CI filure issue * Prob whether waitid supports P_PIDFD instead of checking for Linux kernel version * Use as a platform-independent term to describe pidfd(Linux)/process handle (Windows)
1 parent 7fb7ee8 commit 8fdb554

File tree

12 files changed

+738
-404
lines changed

12 files changed

+738
-404
lines changed

Sources/Subprocess/Configuration.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,12 @@ public struct Configuration: Sendable {
103103
// even if `body` throws, and we are not leaving zombie processes in the
104104
// process table which will cause the process termination monitoring thread
105105
// to effectively hang due to the pid never being awaited
106-
let terminationStatus = try await Subprocess.monitorProcessTermination(forExecution: _spawnResult.execution)
106+
let terminationStatus = try await Subprocess.monitorProcessTermination(
107+
for: execution.processIdentifier
108+
)
109+
110+
// Close process file descriptor now we finished monitoring
111+
execution.processIdentifier.close()
107112

108113
return try ExecutionResult(terminationStatus: terminationStatus, value: result.get())
109114
}

Sources/Subprocess/Execution.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,11 @@ public struct Execution: Sendable {
3434
/// The process identifier of the current execution
3535
public let processIdentifier: ProcessIdentifier
3636

37-
#if os(Windows)
38-
internal nonisolated(unsafe) let processInformation: PROCESS_INFORMATION
39-
internal let consoleBehavior: PlatformOptions.ConsoleBehavior
40-
41-
init(
42-
processIdentifier: ProcessIdentifier,
43-
processInformation: PROCESS_INFORMATION,
44-
consoleBehavior: PlatformOptions.ConsoleBehavior
45-
) {
46-
self.processIdentifier = processIdentifier
47-
self.processInformation = processInformation
48-
self.consoleBehavior = consoleBehavior
49-
}
50-
#else
5137
init(
5238
processIdentifier: ProcessIdentifier
5339
) {
5440
self.processIdentifier = processIdentifier
5541
}
56-
#endif // os(Windows)
57-
58-
internal func release() {
59-
#if os(Windows)
60-
guard CloseHandle(processInformation.hThread) else {
61-
fatalError("Failed to close thread HANDLE: \(SubprocessError.UnderlyingError(rawValue: GetLastError()))")
62-
}
63-
guard CloseHandle(processInformation.hProcess) else {
64-
fatalError("Failed to close process HANDLE: \(SubprocessError.UnderlyingError(rawValue: GetLastError()))")
65-
}
66-
#endif
67-
}
6842
}
6943

7044
// MARK: - Output Capture

Sources/Subprocess/Platforms/Subprocess+Darwin.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -477,26 +477,41 @@ extension Configuration {
477477
}
478478
}
479479

480-
// Special keys used in Error's user dictionary
481-
extension String {
482-
static let debugDescriptionErrorKey = "NSDebugDescription"
480+
// MARK: - ProcessIdentifier
481+
482+
/// A platform independent identifier for a Subprocess.
483+
public struct ProcessIdentifier: Sendable, Hashable {
484+
/// The platform specific process identifier value
485+
public let value: pid_t
486+
487+
public init(value: pid_t) {
488+
self.value = value
489+
}
490+
491+
internal func close() { /* No-op on Darwin */ }
492+
}
493+
494+
extension ProcessIdentifier: CustomStringConvertible, CustomDebugStringConvertible {
495+
public var description: String { "\(self.value)" }
496+
497+
public var debugDescription: String { "\(self.value)" }
483498
}
484499

485500
// MARK: - Process Monitoring
486501
@Sendable
487502
internal func monitorProcessTermination(
488-
forExecution execution: Execution
503+
for processIdentifier: ProcessIdentifier
489504
) async throws -> TerminationStatus {
490505
return try await withCheckedThrowingContinuation { continuation in
491506
let source = DispatchSource.makeProcessSource(
492-
identifier: execution.processIdentifier.value,
507+
identifier: processIdentifier.value,
493508
eventMask: [.exit],
494509
queue: .global()
495510
)
496511
source.setEventHandler {
497512
source.cancel()
498513
var siginfo = siginfo_t()
499-
let rc = waitid(P_PID, id_t(execution.processIdentifier.value), &siginfo, WEXITED)
514+
let rc = waitid(P_PID, id_t(processIdentifier.value), &siginfo, WEXITED)
500515
guard rc == 0 else {
501516
continuation.resume(
502517
throwing: SubprocessError(

0 commit comments

Comments
 (0)