Hello,
I was wondering if anyone could help explain something to me.
I am attempting to use Foundation's Process to run 'ps' to list active processes.
I have the following code as listed at the bottom of this post.
If the line marked //1 is unaltered then the process runs 'ps -axc -o comm' and works. The process runs and completes and stdOut contains the output.
Testing Foundation's Process with 'ps'
ps5(alternateFlags:)
running file:///bin/ps ["-axc", "-o comm"]
ps5(alternateFlags:) ends.
terminationHandler
ps process was terminated (NSTaskTerminationReason(rawValue: 1)
StdOut:
COMM
launchd
logd
smd
UserEventAgent
<.... Lots of processes snipped here .... >
However, if in line //1 the alternateFlags argument is set to true then the process runs 'ps -ax' and doesn't work. The process runs and never completes.
Testing Foundation's Process with 'ps'
ps5(alternateFlags:)
running file:///bin/ps ["-ax"]
ps5(alternateFlags:) ends.
Program ended with exit code: 9
Why is this? Is it a macOS sandbox thing? Both versions work on Linux. (n.b. the code needs to be altered slightly on Linux to compile correctly due to missing API in Foundation.)
import Foundation
print("Testing Foundation's Process with 'ps'")
let psBinaryPath = "/bin/ps"
let app = App()
app.run()
struct App {
let runLoop = RunLoop.current
let distantFuture = Date.distantFuture
/// Set this to false when we want to exit the app...
let shouldKeepRunning = true
func run() {
// 1
ps5(alternateFlags: false)
// Run forever
while shouldKeepRunning == true &&
runLoop.run(mode:.default, before: distantFuture) {}
}
}
func ps5(alternateFlags: Bool = false) {
print(#function)
let stdOutPipe = Pipe()
let stdErrPipe = Pipe()
let psProcess = Process()
// Using executableURL, which is more modern.
if #available(macOS 13.0, *) {
psProcess.executableURL = URL(filePath: psBinaryPath)
} else {
psProcess.executableURL = URL(fileURLWithPath: psBinaryPath)
}
//2 // This Process ends
psProcess.arguments = ["-axc", "-o comm"]
if alternateFlags {
//3 // This Process doesn't end
psProcess.arguments = ["-ax"]
}
psProcess.standardOutput = stdOutPipe
psProcess.standardError = stdErrPipe
// What happens when process ends.
psProcess.terminationHandler = { process in
print("terminationHandler")
print("ps process was terminated (\(process.terminationReason)")
if process.terminationReason != .exit {
print("ps process ended badly")
let outputData = stdErrPipe.fileHandleForReading.readDataToEndOfFile()
let outputString = String(data: outputData, encoding: .utf8) ?? "No StdErr available"
print(outputString)
return
}
print("StdOut:")
let outputData = stdOutPipe.fileHandleForReading.readDataToEndOfFile()
let outputString = String(data: outputData, encoding: .utf8) ?? "No StdOut available"
print(outputString)
}
// Run the process
do {
print("running \(psProcess.executableURL!) \(psProcess.arguments!)")
try psProcess.run()
} catch {
print("err: \(error)")
}
print("\(#function) ends.")
}
OK, I ran your updated sample and itâs behaving correctly. The relevant RunLoop documentation warns:
Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiverâs thread. Those sources could therefore prevent the run loop from exiting.
If you want the run loop to terminate, you shouldn't use this method. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop.
You need to set shouldKeepRunning to false in your termination handler.
It's working correctly even if you alter the line marked //1 to pass false?
If we ignore the Runloop aspect for now, here's a version as a simple AppKit app.
You should see that the behaviour is different depending on whether you have the checkbox checked or not.