@@ -63,52 +63,97 @@ struct SubprocessLinuxTests {
6363 }
6464
6565 @Test func testSuspendResumeProcess( ) async throws {
66- func isProcessSuspended( _ pid: pid_t ) throws -> Bool {
67- let status = try Data (
68- contentsOf: URL ( filePath: " /proc/ \( pid) /status " )
69- )
70- let statusString = try #require(
71- String ( data: status, encoding: . utf8)
72- )
73- // Parse the status string
74- let stats = statusString. split ( separator: " \n " )
75- if let index = stats. firstIndex (
76- where: { $0. hasPrefix ( " State: " ) }
77- ) {
78- let processState = stats [ index] . split (
79- separator: " : "
80- ) . map {
81- $0. trimmingCharacters (
82- in: . whitespacesAndNewlines
83- )
84- }
85-
86- return processState [ 1 ] . hasPrefix ( " T " )
87- }
88- return false
89- }
90-
9166 _ = try await Subprocess . run (
9267 // This will intentionally hang
9368 . path( " /usr/bin/sleep " ) ,
9469 arguments: [ " infinity " ] ,
9570 error: . discarded
9671 ) { subprocess, standardOutput in
97- // First suspend the process
98- try subprocess. send ( signal: . suspend)
99- #expect(
100- try isProcessSuspended ( subprocess. processIdentifier. value)
101- )
102- // Now resume the process
103- try subprocess. send ( signal: . resume)
104- #expect(
105- try isProcessSuspended ( subprocess. processIdentifier. value) == false
106- )
107- // Now kill the process
108- try subprocess. send ( signal: . terminate)
109- for try await _ in standardOutput { }
72+ try await tryFinally {
73+ // First suspend the process
74+ try subprocess. send ( signal: . suspend)
75+ try await waitForCondition ( timeout: . seconds( 30 ) ) {
76+ let state = try subprocess. state ( )
77+ return state == . stopped
78+ }
79+ // Now resume the process
80+ try subprocess. send ( signal: . resume)
81+ try await waitForCondition ( timeout: . seconds( 30 ) ) {
82+ let state = try subprocess. state ( )
83+ return state == . running
84+ }
85+ } finally: { error in
86+ // Now kill the process
87+ try subprocess. send ( signal: error != nil ? . kill : . terminate)
88+ for try await _ in standardOutput { }
89+ }
11090 }
11191 }
11292}
11393
94+ fileprivate enum ProcessState : String {
95+ case running = " R "
96+ case sleeping = " S "
97+ case uninterruptibleWait = " D "
98+ case zombie = " Z "
99+ case stopped = " T "
100+ }
101+
102+ extension Execution {
103+ fileprivate func state( ) throws -> ProcessState {
104+ let processStatusFile = " /proc/ \( processIdentifier. value) /status "
105+ let processStatusData = try Data (
106+ contentsOf: URL ( filePath: processStatusFile)
107+ )
108+ let stateMatches = try String ( decoding: processStatusData, as: UTF8 . self)
109+ . split ( separator: " \n " )
110+ . compactMap ( { line in
111+ return try #/^State:\s+(?<status>[A-Z])\s+.*/# . wholeMatch ( in: line)
112+ } )
113+ guard let status = stateMatches. first, stateMatches. count == 1 , let processState = ProcessState ( rawValue: String ( status. output. status) ) else {
114+ struct ProcStatusParseError : Error , CustomStringConvertible {
115+ let filePath : String
116+ let contents : Data
117+ var description : String {
118+ " Could not parse \( filePath) : \n \( String ( decoding: contents, as: UTF8 . self) ) "
119+ }
120+ }
121+ throw ProcStatusParseError ( filePath: processStatusFile, contents: processStatusData)
122+ }
123+ return processState
124+ }
125+ }
126+
127+ func waitForCondition( timeout: Duration , _ evaluateCondition: ( ) throws -> Bool ) async throws {
128+ var currentCondition = try evaluateCondition ( )
129+ let deadline = ContinuousClock . now + timeout
130+ while ContinuousClock . now < deadline {
131+ if currentCondition {
132+ return
133+ }
134+ try await Task . sleep ( for: . milliseconds( 10 ) )
135+ currentCondition = try evaluateCondition ( )
136+ }
137+ struct TimeoutError : Error , CustomStringConvertible {
138+ var description : String {
139+ " Timed out waiting for condition to be true "
140+ }
141+ }
142+ throw TimeoutError ( )
143+ }
144+
145+ func tryFinally( _ work: ( ) async throws -> ( ) , finally: ( Error ? ) async throws -> ( ) ) async throws {
146+ let error : Error ?
147+ do {
148+ try await work ( )
149+ error = nil
150+ } catch let e {
151+ error = e
152+ }
153+ try await finally ( error)
154+ if let error {
155+ throw error
156+ }
157+ }
158+
114159#endif // canImport(Glibc) || canImport(Bionic) || canImport(Musl)
0 commit comments