Skip to content

Commit 6a6b505

Browse files
authored
Merge pull request #3113 from pcbeard/AvoidWaiting
Allow `Process.waitUntilExit()` to be called from any thread
2 parents fec39a6 + afa6b1e commit 6a6b505

File tree

1 file changed

+60
-57
lines changed

1 file changed

+60
-57
lines changed

Sources/Foundation/Process.swift

Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ open class Process: NSObject {
576576

577577
var environment: [String:String] = [:]
578578
if let env = self.environment {
579-
environment = env
579+
environment = env
580580
} else {
581581
environment = ProcessInfo.processInfo.environment
582582
}
@@ -600,67 +600,65 @@ open class Process: NSObject {
600600
context.release = runLoopSourceRelease
601601
context.info = Unmanaged.passUnretained(self).toOpaque()
602602

603-
let socket: CFSocket =
604-
CFSocketCreateWithNative(nil, CFSocketNativeHandle(sockets.first), CFOptionFlags(kCFSocketDataCallBack), { (socket, type, address, data, info) in
605-
let process: Process = NSObject.unretainedReference(info!)
606-
process.processLaunchedCondition.lock()
607-
while process.isRunning == false {
608-
process.processLaunchedCondition.wait()
609-
}
610-
process.processLaunchedCondition.unlock()
611-
612-
WaitForSingleObject(process.processHandle, WinSDK.INFINITE)
613-
614-
// On Windows, the top nibble of an NTSTATUS indicates severity, with
615-
// the top two bits both being set (0b11) indicating an error. In
616-
// addition, in a well formed NTSTATUS, the 4th bit must be 0.
617-
// The third bit indicates if the error is a Microsoft defined error
618-
// and may or may not be set.
619-
//
620-
// If we receive such an error, we'll indicate that the process
621-
// exited abnormally (confusingly indicating "signalled" so we match
622-
// POSIX behaviour for abnormal exits).
623-
//
624-
// However, we don't want user programs which normally exit -1, -2,
625-
// etc to count as exited abnormally, so we specifically check for a
626-
// top nibble of 0b11_0 so that e.g. 0xFFFFFFFF, won't trigger an
627-
// abnormal exit.
628-
//
629-
// Additionally, on Windows, an uncaught signal terminates the
630-
// program with the magic exit code 3, regardless of the signal (I'd
631-
// personally love to know the reason for this). So we also consider
632-
// 3 to be an abnormal exit.
633-
var dwExitCode: DWORD = 0
634-
GetExitCodeProcess(process.processHandle, &dwExitCode)
635-
if (dwExitCode & 0xF0000000) == 0xC0000000
603+
let socket: CFSocket = CFSocketCreateWithNative(nil, CFSocketNativeHandle(sockets.first), CFOptionFlags(kCFSocketDataCallBack), { (socket, type, address, data, info) in
604+
let process: Process = NSObject.unretainedReference(info!)
605+
process.processLaunchedCondition.lock()
606+
while process.isRunning == false {
607+
process.processLaunchedCondition.wait()
608+
}
609+
process.processLaunchedCondition.unlock()
610+
611+
WaitForSingleObject(process.processHandle, WinSDK.INFINITE)
612+
613+
// On Windows, the top nibble of an NTSTATUS indicates severity, with
614+
// the top two bits both being set (0b11) indicating an error. In
615+
// addition, in a well formed NTSTATUS, the 4th bit must be 0.
616+
// The third bit indicates if the error is a Microsoft defined error
617+
// and may or may not be set.
618+
//
619+
// If we receive such an error, we'll indicate that the process
620+
// exited abnormally (confusingly indicating "signalled" so we match
621+
// POSIX behaviour for abnormal exits).
622+
//
623+
// However, we don't want user programs which normally exit -1, -2,
624+
// etc to count as exited abnormally, so we specifically check for a
625+
// top nibble of 0b11_0 so that e.g. 0xFFFFFFFF, won't trigger an
626+
// abnormal exit.
627+
//
628+
// Additionally, on Windows, an uncaught signal terminates the
629+
// program with the magic exit code 3, regardless of the signal (I'd
630+
// personally love to know the reason for this). So we also consider
631+
// 3 to be an abnormal exit.
632+
var dwExitCode: DWORD = 0
633+
GetExitCodeProcess(process.processHandle, &dwExitCode)
634+
if (dwExitCode & 0xF0000000) == 0xC0000000
636635
|| (dwExitCode & 0xF0000000) == 0xE0000000
637636
|| dwExitCode == 3 {
638-
process._terminationStatus = Int32(dwExitCode & 0x3FFFFFFF)
639-
process._terminationReason = .uncaughtSignal
640-
} else {
641-
process._terminationStatus = Int32(bitPattern: dwExitCode)
642-
process._terminationReason = .exit
643-
}
637+
process._terminationStatus = Int32(dwExitCode & 0x3FFFFFFF)
638+
process._terminationReason = .uncaughtSignal
639+
} else {
640+
process._terminationStatus = Int32(bitPattern: dwExitCode)
641+
process._terminationReason = .exit
642+
}
644643

645-
if let handler = process.terminationHandler {
646-
let thread: Thread = Thread { handler(process) }
647-
thread.start()
648-
}
644+
if let handler = process.terminationHandler {
645+
let thread: Thread = Thread { handler(process) }
646+
thread.start()
647+
}
649648

650-
process.isRunning = false
649+
process.isRunning = false
651650

652-
// Invalidate the source and wake up the run loop, if it is available
653-
CFRunLoopSourceInvalidate(process.runLoopSource)
654-
if let runloop = process.runLoop {
655-
CFRunLoopWakeUp(runloop._cfRunLoop)
656-
}
651+
// Invalidate the source and wake up the run loop, if it is available
652+
CFRunLoopSourceInvalidate(process.runLoopSource)
653+
if let runloop = process.runLoop {
654+
CFRunLoopWakeUp(runloop._cfRunLoop)
655+
}
657656

658-
CFSocketInvalidate(socket)
657+
CFSocketInvalidate(socket)
659658
}, &context)
660659
CFSocketSetSocketFlags(socket, CFOptionFlags(kCFSocketCloseOnInvalidate))
661660

662-
let source: CFRunLoopSource =
663-
CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0)
661+
let source: CFRunLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0)
664662
CFRunLoopAddSource(managerThreadRunLoop?._cfRunLoop, source, kCFRunLoopDefaultMode)
665663

666664
let workingDirectory = currentDirectoryURL?.path ?? FileManager.default.currentDirectoryPath
@@ -1153,10 +1151,15 @@ open class Process: NSObject {
11531151

11541152
// poll the runLoop in defaultMode until process completes
11551153
open func waitUntilExit() {
1156-
1157-
repeat {
1158-
1159-
} while( self.isRunning == true && RunLoop.current.run(mode: .default, before: Date(timeIntervalSinceNow: 0.05)) )
1154+
let runInterval = 0.05
1155+
let currentRunLoop = RunLoop.current
1156+
let checkRunLoop : () -> Bool = (currentRunLoop == self.runLoop)
1157+
? { currentRunLoop.run(mode: .default, before: Date(timeIntervalSinceNow: runInterval)) }
1158+
: { currentRunLoop.run(until: Date(timeIntervalSinceNow: runInterval)); return true }
1159+
1160+
// update .runLoop to allow early wakeup.
1161+
self.runLoop = currentRunLoop
1162+
while self.isRunning && checkRunLoop() {}
11601163

11611164
self.runLoop = nil
11621165
self.runLoopSource = nil

0 commit comments

Comments
 (0)