【问题标题】:cURL through NSTask not terminating if a pipe is present如果存在管道,则通过 NSTask 的 cURL 不会终止
【发布时间】:2016-03-15 10:52:09
【问题描述】:

我正在尝试为 Swift 中的简单命令行批处理脚本同步读取 URL 的内容。为了简单起见,我使用 cURL - 我知道如果必须,我可以使用 NSURLSession。我也在用swift build 在 OSX 上使用开源版本的 Swift 构建它。

问题在于,在某些 URL 上,如果标准输出已被重定向到管道,则 NSTask 永远不会终止。

// This will hang, and when terminated with Ctrl-C reports "(23) Failed writing body"
import Foundation
let task = NSTask()
let pipe = NSPipe()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704647"]
task.standardOutput = pipe
task.launch()
task.waitUntilExit()

但是,如果您移除管道或更改 URL,则任务会成功。

// This will succeed - no pipe
import Foundation
let task = NSTask()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704647"]
task.launch()
task.waitUntilExit()

// This will succeed - different URL
import Foundation
let task = NSTask()
let pipe = NSPipe()
task.launchPath = "/usr/bin/curl"
task.arguments = ["http://trove.nla.gov.au/newspaper/page/21704646"]
task.standardOutput = pipe
task.launch()
task2.waitUntilExit()

从终端直接使用 curl 运行任何示例都会成功,因此当从特定 URL(和其他一些 URL)检索时,以及存在管道时,与 NSTask 的交互存在一些问题,这会导致 cURL失败。

【问题讨论】:

  • 愚蠢的问题;但是您确定要在这里使用“let”而不是“var”。如果任务产生输出,它是否会被分配回变量本身?
  • 对于管道,您的意思是?输出被传递到可以从中读取它的管道。输出不会替换 NSPipe 实例本身。
  • 好的,但是我在这段代码中没有看到“var”,只有“let”;没有任何东西被传回给任何东西?
  • 当然,这是一个最小的例子。在生产代码中,标准输出的内容被解析并返回给调用上下文。但这个例子足以说明问题——task3挂了,而task1和task2成功完成。
  • 托比;确定您遇到了某种竞争条件或锁定问题,您是否尝试过将每个进程移动到自己的线程?

标签: swift nstask


【解决方案1】:

扩展一点@Hod 的回答:已启动的标准输出 进程被重定向到管道,但您的程序永远不会从 另一个管端。管道有一个 有限的缓冲区, 例如 How big is the pipe buffer? 这解释了 macOS 上的管道缓冲区大小(最多)为 64KB。

如果管道缓冲区已满,则启动的进程无法对其进行写入 了。如果进程使用阻塞 I/O,那么到管道的write() 将阻塞,直到可以写入至少一个字节。确实如此 在你的情况下永远不会发生,所以进程挂起并且不会终止。

只有写入标准输出的数量才会出现问题 超过了管道缓冲区大小,这就解释了为什么它只发生在某些 URL 而不是其他 URL。

作为解决方案,您可以从管道中读取,例如与

let data = pipe.fileHandleForReading.readDataToEndOfFile()

之前等待进程终止。另一种选择是 使用异步读取,例如来自Real time NSTask output to NSTextView with Swift的代码:

pipe.fileHandleForReading.readabilityHandler = { fh in
    let data = fh.availableData
    // process data ...
}

这也将允许读取标准输出和标准错误 从一个进程通过管道没有阻塞。

【讨论】:

    【解决方案2】:

    curl 和 NSPipe 缓冲数据。根据您在 ctrl-c out 时遇到的错误(这表明 curl 无法写入预期的数据量),您之间的交互很糟糕。

    尝试将 -N 选项添加到 curl 以防止它缓冲其输出。

    curl 也可以输出进度。我认为这不会导致问题,但您可以添加 -s 以仅获取数据以防万一。

    【讨论】:

    • 请注意,作为赏金的贡献者,我对一个解释/解决方案感兴趣,该解决方案专注于管道规范如何干扰任务完成的一般问题。根据我的评论,我使用 swiftc 编译器和 stderr 遇到了这个问题,而不是 cURL 和 stdout。
    • @PeterAlfvin 你能发布一个代码示例吗?我无法使用 stderr 重现问题。
    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 2019-12-21
    • 2019-12-26
    • 1970-01-01
    • 2018-07-06
    • 1970-01-01
    • 2018-01-26
    相关资源
    最近更新 更多