【问题标题】:Getting stdout from Process (NSTask) sometimes fails从 Process (NSTask) 获取标准输出有时会失败
【发布时间】:2019-02-19 23:04:54
【问题描述】:

我在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
...

但是我想不出一种方法来说“等到每个可读性回调都完成执行”。甚至可能吗?可能我需要一些其他的 api 来做到这一点?乍一看,这似乎是一个用高级 api 解决的微不足道的任务。

【问题讨论】:

    标签: process foundation nstask


    【解决方案1】:

    在我看来,FileHandle 没有办法实现这一点,而是我已经使用了较低的 api。

    如果有人感兴趣,SwiftPM 实用程序中实际上有非常相似的代码 - https://github.com/apple/swift-package-manager/blob/master/Sources/Basic/Process.swift(参见 Process.popenProcess.checkNonZeroExit

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-02-07
      • 1970-01-01
      • 2020-11-14
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      相关资源
      最近更新 更多