Segmentation fault in Linux with Swift 5.4 when using Grand Central Dispatch

Here's one that has me stumped. The following code runs just fine under MacOS but causes a Segmentation fault under Linux. Under both systems I'm using Swift 5.4.

Linux is running Ubuntu 20.

Any thoughts? If I change the code to use the Thread class then it's fine.

import Foundation
import CoreFoundation

let queue: DispatchQueue = DispatchQueue(label: UUID().uuidString, qos: .utility, attributes: .concurrent)
let group: DispatchGroup = DispatchGroup()
let proc:  Process       = Process()

proc.executableURL = URL(fileURLWithPath: "/usr/bin/iconv")
proc.arguments = [ "-l" ]
proc.standardError = Pipe()
proc.standardOutput = Pipe()

do {
    try proc.run()
}
catch let e {
    try? "\(e)".write(toFile: "/dev/stderr", atomically: false, encoding: String.Encoding.utf8)
}

var _stderr: String = ""
var _stdout: String = ""

queue.async(group: group) { _stderr = String(data: (proc.standardError! as! Pipe).fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8) ?? "" }
queue.async(group: group) { _stdout = String(data: (proc.standardOutput! as! Pipe).fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8) ?? "" }
queue.async(group: group) { proc.waitUntilExit() }
group.wait()

// DOES NOT GET TO THIS PART ON LINUX!

sleep(10)

print("stdout: \(_stdout)")
print("stderr: \(_stderr)")
print("Results: \(proc.terminationStatus)")

If you import Dispatch at the top does that work? IIRC it's implicitly imported on macOS, but not on Linux

I'll try it but it seems to me that it wouldn't even compile if that was the case. I'll let you know the results.

Nope, no difference.

Does this help: Main dispatch queue in Linux/SDL app?

Yup, that seemed to be the problem.

Bottom Line: I had to have my program execute as a job on the main queue and then call dispatchMain(). Of course I also had to call exit(0) from the main queue to get it to stop but I suppose that's a minor nit.

It does create a huge discontinuity between macOS and Linux though (and I'm going to assume Android and Windows as well). I'll have to create a wrapper function for every environment except macOS.

Here's the modified code that works on Linux now.

import Foundation
import CoreFoundation
#if canImport(Darwin)
    import Darwin
#elseif canImport(Glibc)
    import Glibc
#endif

DispatchQueue.main.async {
    let queue: DispatchQueue = DispatchQueue(label: UUID().uuidString, qos: .utility, attributes: .concurrent)
    let group: DispatchGroup = DispatchGroup()
    let proc:  Process       = Process()

    proc.executableURL = URL(fileURLWithPath: "/usr/bin/iconv")
    proc.arguments = [ "-l" ]
    proc.standardError = Pipe()
    proc.standardOutput = Pipe()

    do {
        try proc.run()
    }
    catch let e {
        try? "\(e)".write(toFile: "/dev/stderr", atomically: false, encoding: String.Encoding.utf8)
    }

    var _stderr: String = ""
    var _stdout: String = ""

    queue.async(group: group) { _stderr = String(data: (proc.standardError! as! Pipe).fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8) ?? "" }
    print("================> A")
    queue.async(group: group) { _stdout = String(data: (proc.standardOutput! as! Pipe).fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8) ?? "" }
    print("================> B")
    group.wait()
    print("================> C")
    proc.waitUntilExit()
    print("================> D")
    print("stdout: \(_stdout)")
    print("stderr: \(_stderr)")
    print("Results: \(proc.terminationStatus)")
    print("================> E")
    var ts: timespec = timespec(tv_sec: 10, tv_nsec: 0)
    var tt: timespec = timespec(tv_sec: 0, tv_nsec: 0)
    let rs: Int32    = nanosleep(&ts, &tt)
    print("nanosleep results: \(rs)")
    exit(0)
}

dispatchMain()