@@ -266,32 +266,29 @@ extension String {
266
266
internal func monitorProcessTermination(
267
267
forProcessWithIdentifier pid: ProcessIdentifier
268
268
) async throws -> TerminationStatus {
269
+ _ = setup
269
270
return try await withCheckedThrowingContinuation { continuation in
270
271
_childProcessContinuations. withLock { continuations in
271
- if let existing = continuations. removeValue ( forKey: pid. value) ,
272
- case . status( let existingStatus) = existing
273
- {
274
- // We already have existing status to report
275
- continuation. resume ( returning: existingStatus)
276
- } else {
277
- // Save the continuation for handler
278
- continuations [ pid. value] = . continuation( continuation)
279
- }
272
+ // Save the continuation for handler
273
+ let oldContinuation = continuations. updateValue ( continuation, forKey: pid. value)
274
+ precondition ( oldContinuation == nil )
275
+
276
+ _ = pthread_cond_signal ( _waitThreadNoChildrenCondition)
280
277
}
281
278
}
282
279
}
283
280
284
- private enum ContinuationOrStatus {
285
- case continuation( CheckedContinuation < TerminationStatus , any Error > )
286
- case status( TerminationStatus )
287
- }
288
-
289
- private let _childProcessContinuations :
290
- Mutex <
291
- [ pid_t : ContinuationOrStatus ]
292
- > = Mutex ( [ : ] )
281
+ private let _childProcessContinuations =
282
+ LockedWith <
283
+ pthread_mutex_t ,
284
+ [ pid_t : CheckedContinuation < TerminationStatus , any Error > ]
285
+ > ( )
293
286
294
- private let signalSource : SendableSourceSignal = SendableSourceSignal ( )
287
+ private nonisolated ( unsafe) let _waitThreadNoChildrenCondition = {
288
+ let result = UnsafeMutablePointer< pthread_cond_t> . allocate( capacity: 1 )
289
+ _ = pthread_cond_init ( result, nil )
290
+ return result
291
+ } ( )
295
292
296
293
private extension siginfo_t {
297
294
var si_status : Int32 {
@@ -316,67 +313,71 @@ private extension siginfo_t {
316
313
}
317
314
318
315
private let setup : ( ) = {
319
- signalSource. setEventHandler {
320
- while true {
321
- var siginfo = siginfo_t ( )
322
- guard waitid ( P_ALL, id_t ( 0 ) , & siginfo, WEXITED) == 0 || errno == EINTR else {
323
- return
324
- }
325
- var status : TerminationStatus ? = nil
326
- switch siginfo. si_code {
327
- case . init( CLD_EXITED) :
328
- status = . exited( siginfo. si_status)
329
- case . init( CLD_KILLED) , . init( CLD_DUMPED) :
330
- status = . unhandledException( siginfo. si_status)
331
- case . init( CLD_TRAPPED) , . init( CLD_STOPPED) , . init( CLD_CONTINUED) :
332
- // Ignore these signals because they are not related to
333
- // process exiting
334
- break
335
- default :
336
- fatalError ( " Unexpected exit status: \( siginfo. si_code) " )
337
- }
338
- if let status = status {
339
- _childProcessContinuations. withLock { continuations in
316
+ var thread = pthread_t ( )
317
+ _ = pthread_create (
318
+ & thread,
319
+ nil ,
320
+ { _ -> UnsafeMutableRawPointer ? in
321
+ while true {
322
+ var siginfo = siginfo_t ( )
323
+ if waitid ( P_ALL, id_t ( 0 ) , & siginfo, WEXITED | WNOWAIT) == 0 {
340
324
let pid = siginfo. si_pid
341
- if let existing = continuations. removeValue ( forKey: pid) ,
342
- case . continuation( let c) = existing
343
- {
344
- c. resume ( returning: status)
345
- } else {
346
- // We don't have continuation yet, just state status
347
- continuations [ pid] = . status( status)
325
+ guard pid != 0 , let c = _childProcessContinuations. withLock ( { $0. removeValue ( forKey: pid) } ) else {
326
+ continue
327
+ }
328
+
329
+ c. resume ( with: Result {
330
+ while true {
331
+ var siginfo = siginfo_t ( )
332
+ if waitid ( P_PID, numericCast ( pid) , & siginfo, WEXITED) == 0 {
333
+ var status : TerminationStatus ? = nil
334
+ switch siginfo. si_code {
335
+ case . init( CLD_EXITED) :
336
+ return . exited( siginfo. si_status)
337
+ case . init( CLD_KILLED) , . init( CLD_DUMPED) :
338
+ return . unhandledException( siginfo. si_status)
339
+ default :
340
+ fatalError ( " Unexpected exit status: \( siginfo. si_code) " )
341
+ }
342
+ } else if errno != EINTR {
343
+ throw SubprocessError . UnderlyingError ( rawValue: errno)
344
+ }
345
+ }
346
+ } )
347
+ } else if errno == SIGCHLD {
348
+ _childProcessContinuations. withUnsafeUnderlyingLock { lock, childProcessContinuations in
349
+ if childProcessContinuations. isEmpty {
350
+ _ = pthread_cond_wait ( _waitThreadNoChildrenCondition, lock)
351
+ }
348
352
}
349
353
}
350
354
}
351
- }
352
- }
353
- signalSource . resume ( )
355
+ } ,
356
+ nil
357
+ )
354
358
} ( )
355
359
356
- /// Unchecked Sendable here since this class is only explicitly
357
- /// initialized once during the lifetime of the process
358
- final class SendableSourceSignal : @ unchecked Sendable {
359
- private let signalSource : DispatchSourceSignal
360
+ private func _setupMonitorSignalHandler ( ) {
361
+ // Only executed once
362
+ setup
363
+ }
360
364
361
- func setEventHandler( handler: @escaping DispatchSourceHandler ) {
362
- self . signalSource. setEventHandler ( handler: handler)
365
+ extension pthread_mutex_t : Lockable {
366
+ static func initializeLock( at lock: UnsafeMutablePointer < Self > ) {
367
+ _ = pthread_mutex_init ( lock, nil )
363
368
}
364
369
365
- func resume ( ) {
366
- self . signalSource . resume ( )
370
+ static func deinitializeLock ( at lock : UnsafeMutablePointer < Self > ) {
371
+ _ = pthread_mutex_destroy ( lock )
367
372
}
368
373
369
- init ( ) {
370
- self . signalSource = DispatchSource . makeSignalSource (
371
- signal: SIGCHLD,
372
- queue: . global( )
373
- )
374
+ static func unsafelyAcquireLock( at lock: UnsafeMutablePointer < Self > ) {
375
+ _ = pthread_mutex_lock ( lock)
374
376
}
375
- }
376
377
377
- private func _setupMonitorSignalHandler ( ) {
378
- // Only executed once
379
- setup
378
+ static func unsafelyRelinquishLock ( at lock : UnsafeMutablePointer < Self > ) {
379
+ _ = pthread_mutex_unlock ( lock )
380
+ }
380
381
}
381
382
382
383
#endif // canImport(Glibc) || canImport(Android) || canImport(Musl)
0 commit comments