Process.run unrecoverable error

I have a long running process on linux that periodically launches a child process using Process, it looks something like this:

func doThing() throws -> Data {
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/usr/bin/dothing")
    process.arguments = [
        "-o", "-", // output to stdout
    ]
    let pipe = Pipe()
    process.standardOutput = pipe
    try process.run()
    return pipe.fileHandleForReading.readDataToEndOfFile()
}

My problem is that this after running fine for some time (usually 24h+ with 1000s of successful
launches) my process crashes with a: "Fatal error: POSIX command failed with error: 9 -- EBADF".

Am I doing something wrong that could cause this by passing the pipe and reading the data like this? And why is a throwing function calling fatalError in the first place?

Process should throw rather than fatalError if possible so that does look like a bug.
The other issue is that Pipe() doesnt have a nil-able or throwing initialiser even if it cant open a pipe. That could indicate that pipes arent being freed correctly and the process is running out of pipes or file descriptors.

The simplest workaround you can do here would be to add a check that the pipe is valid:

guard pipe.fileHandleForWriting.fileDescriptor != -1 && pipe.fileHandleForReading.fileDescriptor != -1 else { throw ... }
1 Like

Thanks! That makes sense, I will try that.

Was just reading the code for Process and it looks like the fatalError calls could be an artefact of the now deprecated launch method.

@spevans where correct in that the pipes are not freed correctly, adding a termination handler to the process and explicitly closing both file handles resolved my issue.

Revised code sample:

func doThing() throws -> Data {
    let process = Process()
    process.executableURL = URL(fileURLWithPath: "/usr/bin/dothing")
    process.arguments = [
        "-o", "-", // output to stdout
    ]
    let pipe = Pipe()
    guard
        pipe.fileHandleForWriting.fileDescriptor != -1,
        pipe.fileHandleForReading.fileDescriptor != -1
    else {
        throw MyError.pipeFail
    }
    process.standardOutput = pipe
    try process.run()
    process.terminationHandler = { _ in
        do {
            try pipe.fileHandleForReading.close()
            try pipe.fileHandleForWriting.close()
        } catch {
            print("Unable to close pipe: \(error.localizedDescription)")
        }
    }
    return pipe.fileHandleForReading.readDataToEndOfFile()
}