Почему exe c .Command.Start () может зависнуть на Дарвине? - PullRequest
2 голосов
/ 21 апреля 2020

Я получаю случайные зависания при запуске конкретной версии программы, которая, кажется, не зависает при использовании официальной версии. Версия dev отличается главным образом тем, что в нее добавлено намного больше библиотек Go std, которые (по большей части) она не выполняет; таким образом, исполняемый файл больше, плюс инициализация stati c -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 в пределах oop. Предполагая, что это всего лишь вызов dup2(), я не могу придумать причину, по которой он зависнет; но я "поймал" зависший тестовый запуск там (и нигде больше) по крайней мере дважды, через delve, хотя, возможно, это просто артефакт использования delve attach <pid> ... против Ctrl- \ ( SIGQUIT ) ?

В течение многих лет (хорошо, десятилетия) я отлаживал и исправлял различные проблемы, связанные с такими действиями, но я относительно новичок в экосистеме Go и не хочу отправлять отчет об ошибках до того, как я иметь некоторое представление о том, что происходит.

В частности, Cmd.Start () задокументировано так:

Запуск запускает указанную команду, но не ждет для его завершения.

Итак, на первый взгляд кажется странным, если не откровенным глючением, что эти зависания указывают на тот самый призыв как на виновника. Т.е. если он не ждет, почему он зависает? Возможно, то, что выглядит как прямой вызов ОС, на самом деле проверяется с помощью механизма потоков Go до или после вызова базовой ОС и там зависает.

Проблема проявляется при запуске набора тестов, который обычно требует около 12 секунд, чтобы бежать. Я выполнил это в циклах продолжительностью около 5 часов, чтобы выполнить git bisect run; хотя это часто срабатывает в течение 15 минут, я видел, что для этого требуется более 3 часов.

Если кто-то хочет вникнуть (ха!) в это более глубоко и, возможно, попытаться воспроизвести его, программа, которую я Я работаю над "Джокером", и вот версия для разработки (мой форк):

https://github.com/jcburley/joker/ (см. ветку gostd; сборка через ./run.sh.)

Проблема возникает (иногда) в OS X при работе ./all-tests.sh. До сих пор зависания происходили только тогда, когда этот скрипт запускает ./flag-tests.sh или ./linter-tests.sh, но еще не ./eval-tests.sh (что также кажется странным, поскольку он всегда запускается первым из-за алфавитного порядка).

Тот же набор тестов выполнялся в al oop на моем устройстве разработки Ubuntu Linux (Ryzen 3) без зависаний более 24 часов. Windows 7 цикл продолжался и в течение нескольких часов, без зависаний до сих пор.

ОБНОВЛЕНИЯ РЕПРО:

  • Репро (пока довольно быстро) с мастером на 6a569f243e028f823a9f20bfd9da7bdfab8699a4
  • git bisect run идентифицировал (golang) виновника как 6becb033341602f2df9d7c55cc23e64b925bbee2; дважды проверил тот и предыдущий коммит (запустив пять экземпляров последнего в течение многих часов), выглядит как solid результат
  • Нет повторений через много часов в Ubuntu Linux Ryzen 3 (amd64- linux) и Windows 7 (amd64- windows, коробка i7 2011-го года)
  • Нет повторения после многих часов тестирования OS X против официального Joker
  • Нет воспроизведения (после нескольких часов) с 1.13.10

Исполняемый файл Joker намного больше для версии dev по сравнению с официальной (основной / выпущенной) версией; хотя большая часть этого дополнительного кода не используется этим небольшим набором тестов, возможно, что некоторый код init () или stati c -var-init, запускаемый из-за добавления дополнительных библиотек (пакетов) Go std, может внести свой вклад более (если не полностью), чем просто размер и проблемы, связанные с размером, из-за запуска дополнительных потоков go и / или ОС, увеличения конкуренции и т. д. c.

...