У меня есть функция-оболочка около Process
, чтобы легко вызывать некоторые внешние процедуры (похожие на pythonic check_output
):
struct Output {
public var code: Int32
public var stdout: String
public var stderr: String
}
func env(workingDir: String, command: [String]) -> Output {
let stdout = Pipe()
let stderr = Pipe()
let process = Process()
process.launchPath = "/usr/bin/env"
process.arguments = command
process.standardError = stderr
process.standardOutput = stdout
process.currentDirectoryPath = workingDir
var out = Data()
var err = Data()
stdout.fileHandleForReading.readabilityHandler = { fh in
out.append(fh.availableData)
}
stderr.fileHandleForReading.readabilityHandler = { fh in
err.append(fh.availableData)
}
process.launch()
process.waitUntilExit()
let code = process.terminationStatus
let outstr = String(data: out, encoding: .utf8) ?? ""
let errstr = String(data: err, encoding: .utf8) ?? ""
return .init(code: code, stdout: outstr, stderr: errstr)
}
К сожалению, иногда это не удается.Я создаю небольшую программу, которая запускает тысячи таких, например:
env(workingDir: ".", command: ["file", "-b", "--mime-type", file.path])
И иногда, очень очень редко, она ничего не выводит с кодом выхода 0.
Я пытался воспроизвестиэто в тестах:
func testEnv() {
let checkEcho: (String) -> () -> () = { mode in {
let speech = "Hello, \(mode) world!"
let output = autoreleasepool {
Process.env(workingDir: ".", command: ["echo", speech])
}
XCTAssertEqual(output.code, 0)
XCTAssertEqual(output.stderr, "")
XCTAssertEqual(output.stdout, speech + "\n")
} }
let performNTimesLoop: (Int, () -> Void) -> Void = {
for _ in 0..<$0 { $1() }
}
let performNTimesConc: (Int, () -> Void) -> Void = { count, code in
DispatchQueue.concurrentPerform(
iterations: count, execute: { _ in code() })
}
performNTimesLoop(1000, checkEcho("serial"))
performNTimesConc(1000, checkEcho("concurrent"))
}
Это очень хорошо подходит для последовательного цикла, но для параллельного цикла он терпит неудачу систематически.Хотя в моей программе нет параллелизма (но я хочу добавить его в ближайшем будущем), я думаю, что причины неудачи могут быть аналогичными.Я попытался добавить несколько замков, семафоров и диспетчерских групп здесь и там, но безуспешно.
Это очень раздражает, поэтому любая помощь будет высоко оценена.Спасибо!
UPD. Как я понимаю, это происходит потому, что вполне возможно, что окончательное создание outstr
будет выполнено до того, как завершится последний обратный вызов (readabilityHandler
), так как оно выполняется в фоновом потоке.,В подтверждение этого сегодня я получил ThreadSanitizer, запущенный на
...
out.append(fh.availableData) // modifying access
...
let outstr = String(data: out, encoding: .utf8) ?? "" //read acces
...
Но я не могу придумать, как сказать: «дождитесь завершения выполнения каждого читабельного callbalck».Это вообще возможно?Может быть, мне нужны другие API для этого?На первый взгляд кажется, что это тривиальная задача, решаемая с помощью API высокого уровня.