Почему выполнение фоновой задачи по ssh завершается неудачей, если выделяется псевдо-tty?

недавно я столкнулся с некоторым немного странным поведением при выполнении команд по ssh. Мне было бы интересно услышать какие-либо объяснения поведения ниже.

под управлением ssh localhost 'touch foobar &' создает файл с именем foobar как и ожидалось:

[bob@server ~]$ ssh localhost 'touch foobar &'
[bob@server ~]$ ls foobar
foobar

однако выполняется та же команда, но с -t возможность принудительного выделения псевдо-tty не удается создать foobar:

[bob@server ~]$ ssh -t localhost 'touch foobar &'
Connection to localhost closed.
[bob@server ~]$ echo $?
0
[bob@server ~]$ ls foobar
ls: cannot access foobar: No such file or directory

моя текущая теория заключается в том, что, поскольку процесс касания псевдо-tty-это распределенного и нераспределенного перед процессом есть шанс бежать. Конечно, добавление одного второго сна позволяет touch работать так, как ожидалось:

[bob@pidora ~]$ ssh -t localhost 'touch foobar & sleep 1'
Connection to localhost closed.
[bob@pidora ~]$ ls foobar
foobar

если у кого-то есть окончательное объяснение, мне было бы очень интересно его услышать. Спасибо.

2 ответов


О, это хорошо.

это связано с тем, как работают группы процессов, как bash ведет себя при вызове в качестве неинтерактивной оболочки с -c, и эффект & в ввода команд.

ответ предполагает, что вы знакомы с тем, как работает управление заданиями в UNIX; если нет, вот представление высокого уровня: каждый процесс принадлежит к группе процессов (процессы в той же группе часто помещаются туда как часть командного конвейера, например cat file | sort | grep 'word' будет место запущенные процессы cat(1), sort(1) и grep(1) в той же группе процессов). bash это процесс, как и любой другой, и он также принадлежит к группе процессов. Группы процессов являются частью сеанса (сеанс состоит из одной или нескольких групп процессов). В сеансе существует не более одной группы процессов, называемой группой процессов переднего плана, и, возможно, много фоновых групп процессов. Группа процессов переднего плана управляет терминалом (если к терминалу присоединен управляющий терминал session); руководитель сеанса (bash) перемещает процессы с фона на передний план и с переднего плана на фон с помощью tcsetpgrp(3). Сигнал, отправленный в группу процессов, доставляется каждому процессу в этой группе.

если концепция групп процессов и управления работой для вас совершенно новая, я думаю, вам нужно будет прочитать об этом, чтобы полностью понять этот ответ. Большой ресурс, чтобы узнать это Глава 9 расширенное программирование в среде UNIX (3-й издание.)

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

в обоих случаях удаленная сторона ssh вызывает bash(1) С -c. The -c флаг причин bash(1) для запуска в качестве неинтерактивной оболочки. Из manpage:

интерактивная оболочка началось без аргументами и без опции-c, стандартный ввод и ошибка которой являются обоими подключить к клеммам (а определяется isatty(3)), или один запущен с опцией-i. PS1 установлен и $ - включает i, Если bash интерактивный, позволяющий скрипту оболочки или файлу запуска проверить это государство.

кроме того, важно знать, что управление заданиями отключается при запуске bash в неинтерактивном режиме. Это означает, что bash не будет создавать отдельную группу процессов для выполнения команды, так как управление заданиями отключено, не будет необходимости перемещать эту команду между передний план и фон, поэтому он может просто оставаться в той же группе процессов, что и bash. Это произойдет независимо от того, принудительно ли вы выделили Pty на ssh с -t.

однако использование & имеет побочный эффект, заставляющий оболочку не ждать завершения команды (даже если управление заданием отключено). Из manpage:

если команда завершается оператором управления&, оболочка выполняет команду в фоновом режиме в подоболочка. Панцирь не ждите завершения команды, и состояние возврата равно 0. Команды, разделенные a; выполняются последовательно; оболочка ожидает для каждой команды, чтобы завершить в свою очередь. Статус возврата-выход состояние последней выполненной команды.

таким образом, в обоих случаях bash не будет ждать выполнения команды, и touch(1) будет выполняться в той же группе процессов, что и bash(1).

Теперь рассмотрим, что происходит, когда сессия лидер уходит. Цитирую setpgid(2) manpage:

если сеанс имеет управляющий терминал и флаг CLOCAL для этого терминал не установлен, и происходит зависание терминала, затем сеанс лидер посылается сигнал SIGHUP. если руководитель сеанса выходит, то вздох сигнал также будет отправлен каждому процессу в процессе переднего плана группа управляющего терминала.

(выделено мной)

когда вы не использовать -t

когда вы не используете -t, на удаленной стороне нет выделения PTY, поэтому bash не является лидером сеанса и фактически не создается новый сеанс. Поскольку sshd работает как демон, процесс bash, который разветвлен + exec () ' d, не будет иметь управляющего терминала. Таким образом, хотя оболочка завершается очень быстро (возможно, раньше touch(1)), нет SIGHUP отправлено в группу процессов, потому что bash не был лидером сеанса (и нет управляющий терминал.) Так что все работает.

при использовании -t

-t принудительное распределение PTY, что означает, что удаленная сторона ssh вызовет setsid(2), выделить псевдо-терминал + вилка новый процесс с forkpty(3), подключите вход и выход главного устройства PTY к конечным точкам сокета, которые ведут к вашей машине, и, наконец, выполните bash(1). forkpty(3) открывает ведомую сторону PTY в раздвоенном процессе, который станет bash; так как нет управляющего терминала для текущего сеанса, и открывается терминальное устройство, устройство PTY становится управляющим терминалом для сеанса, а bash становится лидером сеанса.

то же самое происходит снова: touch(1) выполнена в той же группе процессов, и т. д. да, да. Дело в том,это время is руководитель сеанса и управляющий терминал. Итак, поскольку bash не беспокоит ожидание из-за &, когда он выйдет,SIGHUP поставляется в группу процессов и touch(1) умирает преждевременно.

о nohup

nohup(1) здесь не работает, потому что есть еще гонки. Если bash(1) завершается перед nohup(1) имеет возможность настроить необходимую обработку сигналов и перенаправление файлов, это не будет иметь никакого эффекта (что, вероятно, и происходит)

возможным исправление

сильн повторное включение управления заданиями исправляет это. В bash, вы, что с set -m. Это работает:

ssh -t localhost 'set -m ; touch foobar &'

или заставить Баш ждать touch(1) в комплекте:

ssh -t localhost 'touch foobar & wait `pgrep touch`'

ключ отделяет потоки stdin / stdout/stderr дочернего процесса от исходного сеанса bash/ssh; затем псевдо-распределение tty (ssh -t) больше не требуется, чтобы позволить ребенку пережить прекращение ssh-соединения. См.здесь для полного ответа...