为什么 exec.Command.Start() 会挂在达尔文身上?
Why might exec.Command.Start() hang on darwin?
我偶尔会挂起 运行使用官方版本似乎不会挂起的程序的特定开发版本。 dev 版本的不同之处主要在于它引入了更多的 Go std 库,这些库(在大多数情况下)它不使用;所以可执行文件更大,加上 static-var 和 init() 初始化已经完成,这可能会增加达到某些竞争条件的可能性。
git bisect run
将(golang)罪魁祸首确定为 6becb033341602f2df9d7c55cc23e64b925bbee2
:
Author: Ian Lance Taylor <iant@golang.org>
Date: Thu Apr 11 16:53:11 2019 -0700
[...]
runtime: switch to using new timer code
diff --git a/src/runtime/time.go b/src/runtime/time.go
index fea5d6871c..db48a932d4 100644
--- a/src/runtime/time.go
+++ b/src/runtime/time.go
@@ -14,7 +14,7 @@ import (
)
// Temporary scaffolding while the new timer code is added.
-const oldTimers = true
+const oldTimers = false
// Package time knows the layout of this structure.
// If this struct changes, adjust ../time/sleep.go:/runtimeTimer.
看了一眼这个小改动带来的差异,我强烈倾向于在它启用的 "new timer code" and/or 代码中存在一些竞争条件。
无论是通过 Ctrl-\ (SIGQUIT) 还是 delve attach
,罪魁祸首似乎总是这里的 cmd.Start()
调用:
func sh(dir string, stdin io.Reader, stdout io.Writer, stderr io.Writer, name string, args []string) Object {
cmd := exec.Command(name, args...)
cmd.Dir = dir
cmd.Stdin = stdin
var stdoutBuffer, stderrBuffer bytes.Buffer
if stdout != nil {
cmd.Stdout = stdout
} else {
cmd.Stdout = &stdoutBuffer
}
if stderr != nil {
cmd.Stderr = stderr
} else {
cmd.Stderr = &stderrBuffer
}
err := cmd.Start()
PanicOnErr(err)
那里的堆栈跟踪看起来非常相似,直到到达 syscall/exec_unix.go(在 Go 源代码树中)。然后,在 Delve 中,似乎挂起的是 forkAndExecInChild() 调用,而 Ctrl-\ 显示 readlen() 调用挂起:
// Kick off child.
pid, err1 = forkAndExecInChild(argv0p, argvp, envvp, chroot, dir, attr, sys, p[1])
if err1 != 0 {
err = Errno(err1)
goto error
}
ForkLock.Unlock()
// Read child error status from pipe.
Close(p[1])
n, err = readlen(p[0], (*byte)(unsafe.Pointer(&err1)), int(unsafe.Sizeof(err1)))
Close(p[0])
if err != nil || n != 0 {
forkAndExecInChild()
代码似乎挂在 exec_darwin.go:206
,这是一个循环内对 libc_dup2_trampoline
的系统调用。假设这只是对 dup2()
的调用,我想不出它会挂起的任何原因;但我已经 "caught" 挂起测试 运行 那里(并且没有其他地方)至少两次,通过 delve
,尽管这可能只是使用 delve attach <pid> ...
与 Ctrl- 的神器\ (SIGQUIT)?
多年来(好吧,几十年)我已经调试并修复了围绕此类活动的各种问题,但我对 Go 生态系统相对较新,并且在我对以下内容有所了解之前不想提交错误报告怎么回事。
特别是,Cmd.Start() 记录如下:
Start starts the specified command but does not wait for it to complete.
所以,从表面上看,似乎 st运行ge,如果不是彻头彻尾的错误,这些挂起似乎指向那个调用是罪魁祸首。 IE。如果它不等待,它为什么会挂起?也许看起来像直接 OS 调用实际上在底层 OS 调用之前或之后检查 Go 线程机制,并挂在那里。
问题出现在 运行 测试套件时,通常需要大约 12 秒才能 运行。我 运行 这个循环持续了大约 5 个小时来完成 git bisect run
;虽然它通常在 15 分钟内触发,但我看到它需要 3 个多小时才能完成。
如果有人想更深入地研究(哈哈!)并尝试重现它,我正在研究的程序是 "Joker",这是开发版本(我的叉子):
https://github.com/jcburley/joker/(参见 b运行ch gostd
;通过 ./run.sh
构建。)
运行ning ./all-tests.sh
时,OS X 上(偶尔)出现问题。到目前为止,挂起仅在该脚本 运行s ./flag-tests.sh
或 ./linter-tests.sh
时发生,尚未发生 ./eval-tests.sh
(这似乎也是 st运行ge,因为由于按字母顺序排列,总是首先获得 运行)。
相同的测试套件 运行 在我的 Ubuntu Linux (Ryzen 3) 开发箱上循环运行超过 24 小时,没有挂起。 Windows 7 循环也持续了几个小时,到目前为止没有挂起。
重现更新:
- 从
6a569f243e028f823a9f20bfd9da7bdfab8699a4
起与大师一起重现(到目前为止相当快)
git bisect run
将(golang)罪魁祸首确定为 6becb033341602f2df9d7c55cc23e64b925bbee2
;仔细检查那个和之前的提交(通过 运行 将后者的五个实例持续数小时),看起来是一个可靠的结果
- 在 Ubuntu Linux Ryzen 3 (amd64-linux) 和 Windows 7 (amd64-windows, 2011 上数小时后没有重现-era i7 盒子)
- OS X 测试官方 Joker
数小时后没有重现
- 1.13.10 没有重现(几个小时后)
与官方 (master/released) 版本相比,开发版本的 Joker 可执行文件要大得多;尽管大部分额外的代码都没有被这个小测试套件执行,但可能有一些 init() 或 static-var-init 代码,由于引入了额外的 Go std 库(包)而成为 运行,可能会做出更多贡献(如果不是完全)通过启动额外的 go and/or OS 线程、增加竞争等来解决纯粹的大小和与大小相关的问题
这是 MacOS 版 Go 的一个错误,已在 https://go-review.googlesource.com/c/go/+/372798/
中修复
对于修复之前受影响的 Go 版本,解决方法是将 -Wl,-bind_at_load
传递给链接器,这可以通过使用 -ldflags="-extldflags=-Wl,-bind_at_load"
调用 go 来完成
我偶尔会挂起 运行使用官方版本似乎不会挂起的程序的特定开发版本。 dev 版本的不同之处主要在于它引入了更多的 Go std 库,这些库(在大多数情况下)它不使用;所以可执行文件更大,加上 static-var 和 init() 初始化已经完成,这可能会增加达到某些竞争条件的可能性。
git bisect run
将(golang)罪魁祸首确定为 6becb033341602f2df9d7c55cc23e64b925bbee2
:
Author: Ian Lance Taylor <iant@golang.org>
Date: Thu Apr 11 16:53:11 2019 -0700
[...]
runtime: switch to using new timer code
diff --git a/src/runtime/time.go b/src/runtime/time.go
index fea5d6871c..db48a932d4 100644
--- a/src/runtime/time.go
+++ b/src/runtime/time.go
@@ -14,7 +14,7 @@ import (
)
// Temporary scaffolding while the new timer code is added.
-const oldTimers = true
+const oldTimers = false
// Package time knows the layout of this structure.
// If this struct changes, adjust ../time/sleep.go:/runtimeTimer.
看了一眼这个小改动带来的差异,我强烈倾向于在它启用的 "new timer code" and/or 代码中存在一些竞争条件。
无论是通过 Ctrl-\ (SIGQUIT) 还是 delve attach
,罪魁祸首似乎总是这里的 cmd.Start()
调用:
func sh(dir string, stdin io.Reader, stdout io.Writer, stderr io.Writer, name string, args []string) Object {
cmd := exec.Command(name, args...)
cmd.Dir = dir
cmd.Stdin = stdin
var stdoutBuffer, stderrBuffer bytes.Buffer
if stdout != nil {
cmd.Stdout = stdout
} else {
cmd.Stdout = &stdoutBuffer
}
if stderr != nil {
cmd.Stderr = stderr
} else {
cmd.Stderr = &stderrBuffer
}
err := cmd.Start()
PanicOnErr(err)
那里的堆栈跟踪看起来非常相似,直到到达 syscall/exec_unix.go(在 Go 源代码树中)。然后,在 Delve 中,似乎挂起的是 forkAndExecInChild() 调用,而 Ctrl-\ 显示 readlen() 调用挂起:
// Kick off child.
pid, err1 = forkAndExecInChild(argv0p, argvp, envvp, chroot, dir, attr, sys, p[1])
if err1 != 0 {
err = Errno(err1)
goto error
}
ForkLock.Unlock()
// Read child error status from pipe.
Close(p[1])
n, err = readlen(p[0], (*byte)(unsafe.Pointer(&err1)), int(unsafe.Sizeof(err1)))
Close(p[0])
if err != nil || n != 0 {
forkAndExecInChild()
代码似乎挂在 exec_darwin.go:206
,这是一个循环内对 libc_dup2_trampoline
的系统调用。假设这只是对 dup2()
的调用,我想不出它会挂起的任何原因;但我已经 "caught" 挂起测试 运行 那里(并且没有其他地方)至少两次,通过 delve
,尽管这可能只是使用 delve attach <pid> ...
与 Ctrl- 的神器\ (SIGQUIT)?
多年来(好吧,几十年)我已经调试并修复了围绕此类活动的各种问题,但我对 Go 生态系统相对较新,并且在我对以下内容有所了解之前不想提交错误报告怎么回事。
特别是,Cmd.Start() 记录如下:
Start starts the specified command but does not wait for it to complete.
所以,从表面上看,似乎 st运行ge,如果不是彻头彻尾的错误,这些挂起似乎指向那个调用是罪魁祸首。 IE。如果它不等待,它为什么会挂起?也许看起来像直接 OS 调用实际上在底层 OS 调用之前或之后检查 Go 线程机制,并挂在那里。
问题出现在 运行 测试套件时,通常需要大约 12 秒才能 运行。我 运行 这个循环持续了大约 5 个小时来完成 git bisect run
;虽然它通常在 15 分钟内触发,但我看到它需要 3 个多小时才能完成。
如果有人想更深入地研究(哈哈!)并尝试重现它,我正在研究的程序是 "Joker",这是开发版本(我的叉子):
https://github.com/jcburley/joker/(参见 b运行ch gostd
;通过 ./run.sh
构建。)
运行ning ./all-tests.sh
时,OS X 上(偶尔)出现问题。到目前为止,挂起仅在该脚本 运行s ./flag-tests.sh
或 ./linter-tests.sh
时发生,尚未发生 ./eval-tests.sh
(这似乎也是 st运行ge,因为由于按字母顺序排列,总是首先获得 运行)。
相同的测试套件 运行 在我的 Ubuntu Linux (Ryzen 3) 开发箱上循环运行超过 24 小时,没有挂起。 Windows 7 循环也持续了几个小时,到目前为止没有挂起。
重现更新:
- 从
6a569f243e028f823a9f20bfd9da7bdfab8699a4
起与大师一起重现(到目前为止相当快)
git bisect run
将(golang)罪魁祸首确定为6becb033341602f2df9d7c55cc23e64b925bbee2
;仔细检查那个和之前的提交(通过 运行 将后者的五个实例持续数小时),看起来是一个可靠的结果- 在 Ubuntu Linux Ryzen 3 (amd64-linux) 和 Windows 7 (amd64-windows, 2011 上数小时后没有重现-era i7 盒子)
- OS X 测试官方 Joker 数小时后没有重现
- 1.13.10 没有重现(几个小时后)
与官方 (master/released) 版本相比,开发版本的 Joker 可执行文件要大得多;尽管大部分额外的代码都没有被这个小测试套件执行,但可能有一些 init() 或 static-var-init 代码,由于引入了额外的 Go std 库(包)而成为 运行,可能会做出更多贡献(如果不是完全)通过启动额外的 go and/or OS 线程、增加竞争等来解决纯粹的大小和与大小相关的问题
这是 MacOS 版 Go 的一个错误,已在 https://go-review.googlesource.com/c/go/+/372798/
中修复对于修复之前受影响的 Go 版本,解决方法是将 -Wl,-bind_at_load
传递给链接器,这可以通过使用 -ldflags="-extldflags=-Wl,-bind_at_load"