The problem with a frozen process in swift. Process class

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
}

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

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

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.
4 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?

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!