你正在使用的Go版本是什么( go version
)?
$ go version
go version go1.19.3 darwin/amd64
这个问题在最新版本的发布中是否重现?
是的
你正在使用什么操作系统和处理器架构( go env
)?
go env
输出
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/[REDACTED]/Library/Caches/go-build"
GOENV="/Users/[REDACTED]/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/[REDACTED]/go/pkg/mod"
GONOPROXY="[REDACTED]"
GONOSUMDB="[REDACTED]"
GOOS="darwin"
GOPATH="/Users/[REDACTED]/go"
GOPRIVATE="[REDACTED]"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/Cellar/go/1.19.3/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.19.3/libexec/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.19.3"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cf/t924_8dj02lf0wkmq06hcxsh0000gn/T/go-build892371702=/tmp/go-build -gno-record-gcc-switches -fno-common"
你做了什么?
cmd := exec.Command(myCommand, myArgs...)
stdout, err := cmd.StdoutPipe()
if err != nil {
// log warning or bail out
}
stderr, err := cmd.StderrPipe()
if err != nil {
// log warning, if we bail out we will leak the os.Pipe allocated internally in cmd.StdoutPipe()
}
err = cmd.Start() // if this fails, it *will* clean up both pipes
你期望看到什么?
应该有一些模式可以遵循,以便退出命令,而不泄漏由 StdoutPipe()
和 StderrPipe()
分配的管道。
你看到了什么?
仔细阅读 os/Exec
模块的最新源代码显示,没有清理分配的管道的机制,而不需要首先调用 cmd.Start()
,然后调用 cmd.Wait()
。尽管 StdoutPipe()
和 StderrPipe()
中的错误条件应该很少见(系统可能已经用完了文件描述符),但在生产代码中确实会发生这种情况,尤其是在服务受到负载压力时(例如在拒绝服务攻击期间)。编写“安全”的代码应该是可能的,至少能够对这些情况做出React,而不是加剧问题(例如通过泄漏os.pipe文件描述符)。
讨论
我愿意为这个API限制贡献一个解决方案,但希望社区能就一种惯用方法提供意见。
最简单的方法可能是修改 cmd.Wait()
,即使出错的原因是因为“未启动”(例如 https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/os/exec/exec.go;l=594 ),也要清理管道。否则,可以引入一个新的方法,专门用于清理命令而不启动它(因为为了向后兼容性,我们不能将“清理”函数从 Wait
中分离出来)。
9条答案
按热度按时间bxfogqkk1#
如果你使用一个带有取消上下文的命令运行
Cmd.Start()
,它应该在返回的同时清理描述符。nzrxty8p2#
感谢Seankhliao,这很聪明。也许我们应该在某个地方简单记录一下。
x0fgdtte3#
实际上,在我写这段话的时候,我就想到了这一点。我不会把使用已取消的上下文描述为一种远程直观的行为——文档(也许可以添加到其中一个示例中)可以帮助,但理想情况下,它应该是简单明了的,如何安全地使用API。
mhd8tkvw4#
最简单的方法可能是修改
cmd.Wait()
以清理管道,即使错误的原因是“未启动”。我认为这是有道理的,并且这与现有的指导原则“
Wait
将关闭管道”最为一致。使用已取消上下文调用
Start
的解决方法假设您可以控制Context
和Cmd
的创建方式,并且可能还需要分配一个本来不必要的context.WithCancel
。作为一种解决方法,它并不糟糕,但它似乎不像这样干净。
b0zn9rqh5#
另一个可能的解决方法是自己调用$x_{1m0n1}^{x}$,并通过$x_{1m1n1}^{x}$等字段将其连接起来,但鉴于$x_{1m2n1}^{x}$方法已经存在,我更愿意使它们能够安全地使用。 😅
eufgjt7s6#
目前,如果在未启动时出错,可以安全地重试等待。如果我们改变这一点,会不会打破人们的期望?
hwamh0ep7#
如果调用
Wait
关闭了任何管道,它应该导致随后的Start
返回错误。这个顺序(Pipe
;Wait
;Start
)似乎足够模糊,以至于如果它破坏了任何尚未损坏的现有程序,我会感到惊讶。(我猜大多数在
Wait
之后调用Start
的现有程序都是意外发生的,并且省略了需要进行清理和检查错误的第二个Wait
。)wyyhbhjk8#
也许我们应该为此添加一个Close()函数?
3zwjbxry9#
我认为它不值得拥有一个
Close
函数——它的签名和语义与Wait
完全相同。😅