运行某个程序的特定开发版本时,我偶尔会遇到挂起,而使用官方版本似乎不会挂起。开发版本的不同之处主要在于它引入了更多的 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.
浏览了这个小改动带来的差异后,我强烈倾向于在这个“新计时器代码”和/或它启用的代码中存在一些竞争条件。
无论是通过 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(),我想不出它会挂起的任何原因;但是我已经“捕获”了一个挂起的测试运行在那里(并且没有其他地方)至少两次, via delve,尽管这可能只是使用delve attach <pid> ...与 Ctrl-\ (SIGQUIT)的产物?
DIEA
相关分类