如果存在管道,则通过 NSTask 的 cURL 不会终止 [英] cURL through NSTask not terminating if a pipe is present

查看:29
本文介绍了如果存在管道,则通过 NSTask 的 cURL 不会终止的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to read the contents of a URL synchronously for a simple command-line batch script in Swift. I am using cURL for simplicity's sake - I know I could use NSURLSession if I had to. I am also building this with swift build using the open-source version of Swift on OSX.

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

The problem is that on certain URLs, the NSTask never terminates, if stdout has been redirected to a pipe.

// 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,则任务会成功.

However, if you remove the pipe, or change the URL, the task succeeds.

// 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失败.

Running any of the examples directly using curl from Terminal succeeds, so there is something about the interaction with NSTask, when retrieving from that specific URL (and a few others), and when a pipe is present, that is causing cURL to fail.

推荐答案

在@Hod 的回答上稍微扩展一下:已启动的标准输出进程被重定向到管道,但您的程序永远不会从另一个管端.管道具有有限的缓冲区,参见示例管道缓冲区有多大?这解释了 macOS 上的管道缓冲区大小(最多)为 64KB.

Expanding a little on @Hod's answer: The standard output of the launched process is redirected to a pipe, but your program never reads from the other pipe end. A pipe has a limited buffer, see for example How big is the pipe buffer? which explains that the pipe buffer size on macOS is (at most) 64KB.

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

If the pipe buffer is full then the launched process cannot write on it anymore. If the process uses blocking I/O then a write() to the pipe will block until until at least one byte can be written. That does never happen in your case, so the process hangs and does not terminate.

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

The problem can occur only if the amount written to standard output exceeds the pipe buffer size, which explains why it happens only with certain URLs and not with others.

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

As a solution, you can read from the pipe, e.g. with

let data = pipe.fileHandleForReading.readDataToEndOfFile()

before 等待进程终止.另一种选择是使用异步读取,例如使用 实时 NSTask 输出到 NSTextView 与 Swift 的代码:

before waiting for the process to terminate. Another option is to use asynchronous reading, e.g. with the code from Real time NSTask output to NSTextView with Swift:

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

这也将允许读取标准输出和标准错误从进程通过管道而不阻塞.

That would also allow to read both standard output and standard error from a process via pipes without blocking.

这篇关于如果存在管道,则通过 NSTask 的 cURL 不会终止的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆