8
8
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9
9
//
10
10
11
- #if !SWT_NO_EXIT_TESTS
12
-
11
+ #if !SWT_NO_PROCESS_SPAWNING
13
12
internal import _TestingInternals
14
13
15
14
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD)
@@ -41,8 +40,45 @@ private func _blockAndWait(for pid: consuming pid_t) throws -> ExitCondition {
41
40
}
42
41
}
43
42
}
43
+ #endif
44
+
45
+ #if SWT_TARGET_OS_APPLE && !SWT_NO_LIBDISPATCH
46
+ /// Asynchronously wait for a process to terminate using a dispatch source.
47
+ ///
48
+ /// - Parameters:
49
+ /// - processID: The ID of the process to wait for.
50
+ ///
51
+ /// - Returns: The exit condition of `processID`.
52
+ ///
53
+ /// - Throws: If the exit status of the process with ID `processID` cannot be
54
+ /// determined (i.e. it does not represent an exit condition.)
55
+ ///
56
+ /// This implementation of `wait(for:)` suspends the calling task until
57
+ /// libdispatch reports that `processID` has terminated, then synchronously
58
+ /// calls `_blockAndWait(for:)` (which should not block because `processID` will
59
+ /// have already terminated by that point.)
60
+ ///
61
+ /// - Note: The open-source implementation of libdispatch available on Linux
62
+ /// and other platforms does not support `DispatchSourceProcess`. Those
63
+ /// platforms use an alternate implementation below.
64
+ func wait( for pid: consuming pid_t ) async throws -> ExitCondition {
65
+ let pid = consume pid
66
+
67
+ let source = DispatchSource . makeProcessSource ( identifier: pid, eventMask: . exit)
68
+ defer {
69
+ source. cancel ( )
70
+ }
71
+ await withCheckedContinuation { continuation in
72
+ source. setEventHandler {
73
+ continuation. resume ( )
74
+ }
75
+ source. resume ( )
76
+ }
77
+ withExtendedLifetime ( source) { }
44
78
45
- #if !(SWT_TARGET_OS_APPLE && !SWT_NO_LIBDISPATCH)
79
+ return try _blockAndWait ( for: pid)
80
+ }
81
+ #elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD)
46
82
/// A mapping of awaited child PIDs to their corresponding Swift continuations.
47
83
private let _childProcessContinuations = Locked < [ pid_t : CheckedContinuation < ExitCondition , any Error > ] > ( )
48
84
@@ -54,8 +90,9 @@ private nonisolated(unsafe) let _waitThreadNoChildrenCondition = {
54
90
return result
55
91
} ( )
56
92
57
- /// The implementation of `_createWaitThread()`, run only once.
58
- private let _createWaitThreadImpl : Void = {
93
+ /// Create a waiter thread that is responsible for waiting for child processes
94
+ /// to exit.
95
+ private let _createWaitThread : Void = {
59
96
// The body of the thread's run loop.
60
97
func waitForAnyChild( ) {
61
98
// Listen for child process exit events. WNOWAIT means we don't perturb the
@@ -128,42 +165,29 @@ private let _createWaitThreadImpl: Void = {
128
165
)
129
166
} ( )
130
167
131
- /// Create a waiter thread that is responsible for waiting for child processes
132
- /// to exit.
133
- private func _createWaitThread( ) {
134
- _createWaitThreadImpl
135
- }
136
- #endif
137
-
138
- /// Wait for a given PID to exit and report its status.
168
+ /// Asynchronously wait for a process to terminate using a background thread
169
+ /// that calls `waitid()` in a loop.
139
170
///
140
171
/// - Parameters:
141
- /// - pid: The PID to wait for.
172
+ /// - processID: The ID of the process to wait for.
173
+ ///
174
+ /// - Returns: The exit condition of `processID`.
142
175
///
143
- /// - Returns: The exit condition of `pid`.
176
+ /// - Throws: If the exit status of the process with ID `processID` cannot be
177
+ /// determined (i.e. it does not represent an exit condition.)
178
+ ///
179
+ /// This implementation of `wait(for:)` suspends the calling task until
180
+ /// `waitid()`, called on a shared background thread, reports that `processID`
181
+ /// has terminated, then calls `_blockAndWait(for:)` (which should not block
182
+ /// because `processID` will have already terminated by that point.)
144
183
///
145
- /// - Throws: Any error encountered calling `waitpid()` except for `EINTR`,
146
- /// which is ignored .
184
+ /// On Apple platforms, the libdispatch-based implementation above is more
185
+ /// efficient because it does not need to permanently reserve a thread .
147
186
func wait( for pid: consuming pid_t ) async throws -> ExitCondition {
148
187
let pid = consume pid
149
188
150
- #if SWT_TARGET_OS_APPLE && !SWT_NO_LIBDISPATCH
151
- let source = DispatchSource . makeProcessSource ( identifier: pid, eventMask: . exit)
152
- defer {
153
- source. cancel ( )
154
- }
155
- await withCheckedContinuation { continuation in
156
- source. setEventHandler {
157
- continuation. resume ( )
158
- }
159
- source. resume ( )
160
- }
161
- withExtendedLifetime ( source) { }
162
-
163
- return try _blockAndWait ( for: pid)
164
- #else
165
189
// Ensure the waiter thread is running.
166
- _createWaitThread ( )
190
+ _createWaitThread
167
191
168
192
return try await withCheckedThrowingContinuation { continuation in
169
193
_childProcessContinuations. withLock { childProcessContinuations in
@@ -179,19 +203,23 @@ func wait(for pid: consuming pid_t) async throws -> ExitCondition {
179
203
_ = pthread_cond_signal ( _waitThreadNoChildrenCondition)
180
204
}
181
205
}
182
- #endif
183
206
}
184
207
#elseif os(Windows)
185
- /// Wait for a given process handle to exit and report its status.
208
+ /// Asynchronously wait for a process to terminate using the Windows thread
209
+ /// pool.
186
210
///
187
211
/// - Parameters:
188
- /// - processHandle: The handle to wait for. This function takes ownership of
189
- /// this handle and closes it when done .
212
+ /// - processHandle: A Windows handle representing the process to wait for.
213
+ /// This handle is closed before the function returns .
190
214
///
191
215
/// - Returns: The exit condition of `processHandle`.
192
216
///
193
- /// - Throws: Any error encountered calling `WaitForSingleObject ()` or
217
+ /// - Throws: Any error encountered calling `RegisterWaitForSingleObject ()` or
194
218
/// `GetExitCodeProcess()`.
219
+ ///
220
+ /// This implementation of `wait(for:)` calls `RegisterWaitForSingleObject()` to
221
+ /// wait for `processHandle`, suspends the calling task until the waiter's
222
+ /// callback is called, then calls `GetExitCodeProcess()`.
195
223
func wait( for processHandle: consuming HANDLE ) async throws -> ExitCondition {
196
224
let processHandle = consume processHandle
197
225
defer {
@@ -235,5 +263,8 @@ func wait(for processHandle: consuming HANDLE) async throws -> ExitCondition {
235
263
// FIXME: handle SEH/VEH uncaught exceptions.
236
264
return . exitCode( CInt ( bitPattern: . init( status) ) )
237
265
}
266
+ #else
267
+ #warning("Platform-specific implementation missing: cannot wait for child processes to exit")
268
+ func wait( for processID: consuming Never ) async throws -> ExitCondition { }
238
269
#endif
239
270
#endif
0 commit comments