оболочка: очистить просочившиеся фоновые процессы, которые зависают из-за общего stdout/stderr

Мне нужно запускать практически произвольные команды в (удаленной) оболочке в эфемерных контейнерах/виртуальных машинах для механизма выполнения тестов. Иногда эти фоновые процессы приводят к утечке, что приводит к зависанию всей команды. Это можно свести к этой простой команде:

$ sh -c 'sleep 30 & echo payload'
payload
$

Здесь фоновый sleep 30 играет роль просочившегося процесса (который на самом деле будет чем-то вроде dbus-daemon), а эхо — это то, что я хочу запустить. sleep 30 & echo payload здесь следует рассматривать как пример атомарной непрозрачной команды.

Вышеупомянутая команда в порядке и возвращает немедленно, так как stdout/stderr оболочки, а также спящий режим являются PTY. Однако при захвате вывода команды в канал/файл (в конце концов, тестировщик хочет сохранить все в журнал), вся команда зависает:

$ sh -c 'sleep 30 & echo payload' | cat
payload
# ... does not return to the shell (until the sleep finishes)

Теперь это можно исправить с помощью довольно смехотворно сложной магии оболочки, которая определяет FD stdout/err из /proc/$$/fd/{1,2}, перебирает ls /proc/[0-9]*/fd/* и убивает каждый процесс, который также имеет такой же stdout/stderr. Но это включает в себя много хрупкого кода оболочки и дорогостоящих сравнений строк оболочки.

Есть ли способ очистить эти просочившиеся фоновые процессы более элегантным и простым способом? setsid не помогает:

$ sh -c 'setsid -w sh -c "sleep 30 & echo payload"' | cat
payload
# hangs...

Обратите внимание, что группы/сеансы процессов и полное их уничтожение недостаточно, поскольку просочившиеся процессы (такие как dbus-daemon) часто сами устанавливают id.

P.S. В этих средах я могу предположить только оболочку POSIX или bash; нет Python, Perl и т.д.

Заранее спасибо!


person Martin Pitt    schedule 02.09.2015    source источник
comment
Следующее работает, но я не использую его, чтобы ответить на ваш вопрос: sh -c '{ sleep 30 | cat ; } & { echo payload | cat ; }'   -  person serge-sans-paille    schedule 02.09.2015
comment
Спасибо, но я специально не ищу решение для сна и эха; это просто пример команды для ситуации, когда какая-то команда пропускает фоновую команду. Я добавил некоторые пояснения к вопросу.   -  person Martin Pitt    schedule 02.09.2015
comment
Я нашел начальное приближение: $ setsid -w sh -c 'sleep 30 & echo payload; RC=$?; for p in $(pgrep --pgroup 0); do [ $p = $$ ] || kill $p; done; exit $RC' | cat Простое использование kill -- -$$ слишком агрессивно, так как оно также убивает саму оболочку.   -  person Martin Pitt    schedule 02.09.2015
comment
... но нет, этого недостаточно для реального использования; просочившиеся процессы часто имеют новую группу процессов или даже новый сеанс, поэтому полагаться на них недостаточно; кажется, действительно нужно перебрать все процессы и выловить те, которые совместно используют stdout / stderr, и занести в черный список свою собственную оболочку. вздох   -  person Martin Pitt    schedule 02.09.2015
comment
Не уверен, что можно изменить исходный код, но помогает ли работа в подоболочке? то есть sh -c '(sleep 30 & echo payload|cat)'   -  person cdarke    schedule 02.09.2015
comment
@cdarke: Нет, это не помогает; дело в том, что стандартный вывод самой внешней команды является каналом, т.е. е. ваш пример будет sh -c '(sleep 30 & echo payload)'|cat.   -  person Martin Pitt    schedule 02.09.2015
comment
Хорошо, можно ли вставить disown -a? sh -c '(sleep 30 & echo payload);disown -a|cat' (обратите внимание на позицию в конце кавычки)   -  person cdarke    schedule 02.09.2015


Ответы (3)


У нас была эта проблема с параллельными тестами в Launchpad. Самое простое решение, которое у нас было тогда, и которое работало хорошо, заключалось в том, чтобы убедиться, что никакие процессы не используют общий stdout/stdin/stderr (за исключением тех, которые вы действительно хотите зависнуть, если они не закончились, например, сами тестировщики).

person lifeless    schedule 02.09.2015

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

Другой вариант, не идеальный, но полезный, — стать локальным жнецом с помощью prctl(2) и PR_SET_CHILD_SUBREAPER. Это позволит вам быть родителем всех процессов, которые в противном случае переродились бы в init. При таком расположении вы можете попытаться убить все процессы, которые имеют вас как ppid. Это ужасно, но лучше всего использовать контрольные группы.

Но обратите внимание, что если вы не запускаете этот помощник от имени root, вы обнаружите, что практическое тестирование может породить некоторые setuid вещи, которые будут скрываться и не будут уничтожены. Это действительно раздражающая проблема.

person Zygmunt Krynicki    schedule 02.09.2015
comment
Для той же проблемы, что и в обычном боксе (еще один инструмент тестирования ;-). Вы можете посмотреть на эту ошибку bugs.launchpad.net/plainbox/+bug/1377270, но все детали там одинаковые. Я просто ссылаюсь на него для облегчения перекрестных ссылок. - person Zygmunt Krynicki; 02.09.2015

Используйте script -qfc вместо sh -c.

person Chipaca    schedule 02.09.2015