Hi, I wrote a function that calls linux commands.
But I have a problem, if the called command stacks for some reason, then the swift is waiting for a response and also freezes forever.
I tried to run a timer on a background thread, which after 10 seconds called process.terminate(), but it did not help.
So my app froze forever.
Please help me resolve this problem and kill froze Swift Process.
code:
public func systemCommand(_ command: String, _ user: String? = nil) throws -> String {
var result: String = .init()
let process: Process = .init()
let pipe: Pipe = .init()
process.standardOutput = pipe
process.standardError = pipe
process.executableURL = .init(fileURLWithPath: "/usr/bin/env")
process.arguments = user != nil ? ["sudo", "-H", "-u", user!, "bash", "-lc", "\(command)"] : ["bash", "-lc", "\(command)"]
try process.run()
process.waitUntilExit()
let data: Data = pipe.fileHandleForReading.readDataToEndOfFile()
if let output = String(data: data, encoding: .utf8) {
result = output.trimmingCharacters(in: .whitespacesAndNewlines)
}
if process.isRunning { process.terminate() }
return result
}
jonprescott
(Jonathan Prescott)
2
I see you are trying to use sudo to run a bash shell. How are you planning to provide the password when sudo asks for it? That's probably why you think the whole thing is frozen, it's waiting for the password for sudo. So, for this example, it will always "freeze" since it's waiting for a response for the password.
Try using interrupt() instead of terminate() in your background timer.
@jonprescott
sudo -H -u call script from user if you pass this user to function as parameter. This called from root user and password is not required.
I tried to call interrupt - same result :(
1 Like
I know for sure that the called command frozen. I can debug and I did it for to understand where this is happening. And the called command can actually freeze sometimes.
But the terminate and interrupt - do not kill the process
jonprescott
(Jonathan Prescott)
5
interrupt() sends a SIGINT (CTRL-C) signal, and terminate() seems to send a SIGTERM signal, based on the documentation hints. Both of those can be be masked off by the spawned process. It seems you need to send something like SIGKILL (kill -9 from the command line) which can't be masked off by the spawned process in your background thread. You can send a SIGKILL using the kill system call (man 2 kill). You will need the process ID (PID) from process, which should be the processIdentifier property for process
Martin
(Martin R)
6
A possible problem is that you wait for the process to terminate before reading from the pipe. If the process writes more than what fits into the pipe buffer then you have a deadlock situation.
You might want to read asynchronously from the pipe, e.g. with a readabilityHandler, something like the following (untested) code:
let group = DispatchGroup()
group.enter()
pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
if data.isEmpty { // EOF on the pipe
pipe.fileHandleForReading.readabilityHandler = nil
group.leave()
} else {
result.append(String(data: data, encoding: .utf8)!)
}
}
try process.run()
process.waitUntilExit()
group.wait() // Wait for EOF on the pipe.
5 Likes
@jonprescott
yes I tried kill PID, but I did not try to call with parameter -9
I will try it
@Martin
if call
let data: Data = pipe.fileHandleForReading.readDataToEndOfFile()
insted
process.waitUntilExit()
like this
try process.run()
let data: Data = pipe.fileHandleForReading.readDataToEndOfFile()
process will wait for finish the called command and freeze too
But, I will try your method with callback function too
@woodcrust Does it (Martin method) work for you?
woodcrust
(Ole G )
10
no, but I try @jonprescott method with kill -9 and process is not frozen yet.
But it was an issue when the app process stacked but this issue I couldn't reproduce so I made a fix assuming what was the issue.
Thank you, it works like a charm!
Can't believe that I forgot this since learning C
thanks for saving me a lot of time! 