go os/exec: 如果cmd.Start()从未被调用,会泄漏os.pipes,

wlwcrazw  于 6个月前  发布在  Go
关注(0)|答案(9)|浏览(52)

你正在使用的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 中分离出来)。

bxfogqkk

bxfogqkk1#

如果你使用一个带有取消上下文的命令运行 Cmd.Start(),它应该在返回的同时清理描述符。

nzrxty8p

nzrxty8p2#

感谢Seankhliao,这很聪明。也许我们应该在某个地方简单记录一下。

x0fgdtte

x0fgdtte3#

实际上,在我写这段话的时候,我就想到了这一点。我不会把使用已取消的上下文描述为一种远程直观的行为——文档(也许可以添加到其中一个示例中)可以帮助,但理想情况下,它应该是简单明了的,如何安全地使用API。

mhd8tkvw

mhd8tkvw4#

最简单的方法可能是修改 cmd.Wait() 以清理管道,即使错误的原因是“未启动”。
我认为这是有道理的,并且这与现有的指导原则“ Wait 将关闭管道”最为一致。
使用已取消上下文调用 Start 的解决方法假设您可以控制 ContextCmd 的创建方式,并且可能还需要分配一个本来不必要的 context.WithCancel。作为一种解决方法,它并不糟糕,但它似乎不像

defer func() {
	if err != nil {
		cmd.Wait()  // Clean up any allocated pipes.
	}
}()

这样干净。

b0zn9rqh

b0zn9rqh5#

另一个可能的解决方法是自己调用$x_{1m0n1}^{x}$,并通过$x_{1m1n1}^{x}$等字段将其连接起来,但鉴于$x_{1m2n1}^{x}$方法已经存在,我更愿意使它们能够安全地使用。 😅

eufgjt7s

eufgjt7s6#

目前,如果在未启动时出错,可以安全地重试等待。如果我们改变这一点,会不会打破人们的期望?

hwamh0ep

hwamh0ep7#

如果调用 Wait 关闭了任何管道,它应该导致随后的 Start 返回错误。这个顺序( Pipe ; Wait ; Start )似乎足够模糊,以至于如果它破坏了任何尚未损坏的现有程序,我会感到惊讶。
(我猜大多数在 Wait 之后调用 Start 的现有程序都是意外发生的,并且省略了需要进行清理和检查错误的第二个 Wait 。)

wyyhbhjk

wyyhbhjk8#

也许我们应该为此添加一个Close()函数?

3zwjbxry

3zwjbxry9#

我认为它不值得拥有一个 Close 函数——它的签名和语义与 Wait 完全相同。😅

相关问题