Skip to content

Commit aa44546

Browse files
authored
Merge pull request #3125 from compnerd/sources
Process: correct a subtle `runLoopSource` lifetime issue
2 parents efedfc3 + 27acc07 commit aa44546

File tree

1 file changed

+25
-31
lines changed

1 file changed

+25
-31
lines changed

Sources/Foundation/Process.swift

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -642,18 +642,8 @@ open class Process: NSObject {
642642
process._terminationReason = .exit
643643
}
644644

645-
if let handler = process.terminationHandler {
646-
let thread: Thread = Thread { handler(process) }
647-
thread.start()
648-
}
649-
650-
process.isRunning = false
651-
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-
}
645+
// Signal waitUntilExit() and optionally invoke termination handler.
646+
process.terminateRunLoop()
657647

658648
CFSocketInvalidate(socket)
659649
}, &context)
@@ -829,28 +819,12 @@ open class Process: NSObject {
829819
process._terminationReason = .exit
830820
}
831821

832-
// Set the running flag to false
833-
process.isRunning = false
834-
835-
// If a termination handler has been set, invoke it on a background thread
836-
837-
if let terminationHandler = process.terminationHandler {
838-
let thread = Thread {
839-
terminationHandler(process)
840-
}
841-
thread.start()
842-
}
843-
844-
// Invalidate the source and wake up the run loop, if it's available
845-
846-
CFRunLoopSourceInvalidate(process.runLoopSource)
847-
if let runLoop = process.runLoop {
848-
CFRunLoopWakeUp(runLoop._cfRunLoop)
849-
}
822+
// Signal waitUntilExit() and optionally invoke termination handler.
823+
process.terminateRunLoop()
850824

851825
CFSocketInvalidate( socket )
852826

853-
}, &context )
827+
}, &context )
854828

855829
CFSocketSetSocketFlags( socket, CFOptionFlags(kCFSocketCloseOnInvalidate))
856830

@@ -1165,6 +1139,26 @@ open class Process: NSObject {
11651139
self.runLoop = nil
11661140
self.runLoopSource = nil
11671141
}
1142+
1143+
private func terminateRunLoop() {
1144+
// Ensure that the run loop source is invalidated before we mark the process
1145+
// as no longer running. This serves as a semaphore to
1146+
// `waitUntilExit` to decrement the `runLoopSource` retain count,
1147+
// potentially releasing it.
1148+
CFRunLoopSourceInvalidate(self.runLoopSource)
1149+
let runloopToWakeup = self.runLoop
1150+
self.isRunning = false
1151+
1152+
// Wake up the run loop, *AFTER* clearing .isRunning to avoid an extra time out period.
1153+
if let cfRunLoop = runloopToWakeup?._cfRunLoop {
1154+
CFRunLoopWakeUp(cfRunLoop)
1155+
}
1156+
1157+
if let handler = self.terminationHandler {
1158+
let thread: Thread = Thread { handler(self) }
1159+
thread.start()
1160+
}
1161+
}
11681162
}
11691163

11701164
extension Process {

0 commit comments

Comments
 (0)