Fabric: Локальный терминал stdin отключается, если выполнение ThreadingGroup включает спящий режим

Созданный на 25 июн. 2018  ·  22Комментарии  ·  Источник: fabric/fabric

Я использую группу потоков для запуска команд оболочки. После запуска сценария, который включает sleep , локальный терминал остается с отсоединенным stdin (нажатия клавиш не видны в командной строке), и терминал необходимо перезагрузить.

Я пробовал это довольно много раз и обнаружил, что это происходит только с ThreadingGroups (SerialGroups в порядке). Команда сна может быть в любом месте однострочника (первая команда, середина, последняя) и может быть объединена в одну строчку точкой с запятой или двойным амперсандом. Все команды выполняются должным образом, но терминал остается в плохом состоянии.

Как ни странно, если предыдущий запуск завершился с неперехваченным исключением, это не повлияет на терминал.

Воспроизвести:

from fabric import ThreadingGroup as Group

# raise ValueError()
remotes = Group("host1.example.com", "host2.example.com")
result = remotes.run("echo 1; sleep 1; echo 2")

Запустите сценарий выше. После выхода введите что-нибудь в командной строке. Если вы не видите вывода, <ctrl>+c и введите reset<enter> . Чтобы увидеть поведение после исключения, раскомментируйте строку raise , запустите код, закомментируйте строку и выполните еще два раза. Первый успешный запуск оставит терминал в хорошем состоянии. Второй оставит stdin отсоединенным.

Я обнаружил эту проблему с помощью sleep в своем тестировании, но возможно, что другие команды могут иметь такой же эффект. Также есть шанс, что я просто делаю что-то не так. Если это так, мои извинения.

Моя установка:
Python 3.6.4
ткань 2.1.3
OSX 10.13.5, подключение к Ubuntu 14.04

Bug Needs investigation

Все 22 Комментарий

См. № 1814 как возможный второй воспроизводимый проблемный случай.

Для меня это звучит как законная ошибка, и я не уверен, что ее вызывает. Пахнет так, как будто это может быть общая проблема Unix с оконечными трубами, подключенными к нескольким подпроцессам одновременно, или (особенно в примере # 1814) состояние гонки вокруг состояния канала или что-то в этом роде.

Постараюсь воспроизвести и найти причину / решение.

Кроме того, это, вероятно, требует исправления на уровне Invoke и может быть чисто в его области (поскольку я просто еще не сделал много с потоковой передачей в контексте чистого Invoke; но см., Например, pyinvoke / invoke # 194 - это вещь, которая должно произойти и там). В этом случае я перенесу это в тикет, и «исправление» Fabric будет заключаться в обновлении Invoke после выпуска исправления.

Я был на Ubuntu 16.04.2, подключаясь к тому же.

Еще одно сообщение о той же проблеме в № 1829. Это моя следующая веха по исправлению ошибок, и я, надеюсь, сосредоточусь на этом в следующий день OSS (понедельник).

Я просто пытался воспроизвести это (ветка 2.0, Python 3.6.4, macOS 10.12) и, к сожалению, не смог. Сначала попробовали double-localhost, затем два отдельных удаленных экземпляра облака, в любом случае без костей; потом мой терминал в порядке.

Попробую немного попробовать контейнер Linux на всякий случай, это поможет, но поскольку OP был также на macOS, не уверен, что это будет иметь значение. Я также попробую запустить его в цикле, чтобы увидеть, является ли это случайным воспроизведением.

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

@jensenak @nicktimko вы воспроизводите это в 100% случаев? 50%? 5%?

@bitprophet в 2.1.3 это происходило в моем реальном рабочем процессе довольно часто (> 80%, я также работал параллельно с 6 серверами, а не с 2), хотя в моем надуманном примере из # 1814 это намного ниже, может быть, 20%. Я могу попробовать придумать настройку Docker или, если это не удается, настройку Vagrant для воспроизведения.

@bitprophet Для меня это было 100% времени. На всякий случай я запустил новый virtualenv с установленной только тканью. Я тестировал версии 2.0, 2.1 и 2.2. Вставленный мной пример кода каждый раз приводил к описанному поведению. Во всех тестах я подключался к пультам Ubuntu 14.04.

Я использую другую версию OSX (10.13). Возможно, это связано? Хотя @nicktimko вообще не было на OSX.

Если проблема связана с другой версией, вот как выглядел pip freeze в моем virtualenv:

asn1crypto==0.24.0
bcrypt==3.1.4
cffi==1.11.5
cryptography==2.3
fabric==2.2.1
idna==2.7
invoke==1.1.0
paramiko==2.4.1
pyasn1==0.4.4
pycparser==2.18
PyNaCl==1.2.1
six==1.11.0

Учитывая, что все они были установлены как зависимости Fabric 2.2, я ожидал, что ваши версии будут похожи.

Если я могу чем-то помочь, я более чем готов. Просто не совсем уверен, где еще искать.

С каким коммитом я должен тестировать; вносили ли вы в последнее время какие-либо изменения, которые могут повлиять на ситуацию? Я попробую с замораживанием выше, вы также можете предоставить еще один замороженный reqs.txt и я посмотрю, работает ли это для меня.

@nicktimko @jensenak Спасибо за дополнительную информацию. Я буду пытаться воспроизвести это здесь; при 20% я бы точно не пробовал достаточно, чтобы сработать. Моими пультами были Mac и некоторые старые Debians, я могу попробовать Ubuntu Trusty, если он каким-то образом специфичен для этого (что было бы странно, но эй, все это странно).

Кроме того, каковы ваши локальные среды оболочки? У меня zsh во встроенном Terminal.app (опять же, macOS 10.12) внутри tmux. Я также попробую немного изменить этот угол.

АГА. Кажется, это специфично для bash! По-прежнему не удалось воспроизвести под zsh вне tmux, но как только я пытаюсь под bash, я сразу же получаю упомянутые симптомы. То же самое внутри tmux, поэтому tmux не имеет никакого отношения - это оболочка.

_Почему_ это будет вести себя по-другому под bash и zsh, я не знаю. Может быть, это связано с тем, как они реализованы, или (что более вероятно), может быть, что-то в моих точечных файлах zsh предотвращает проблему? Придется копать ... хотя определение решения на стороне Python, скорее всего, необходимо.

РЕДАКТИРОВАТЬ: также воспроизведение происходит даже при одновременном подключении к sshd моего локального хоста несколько раз, что не слишком удивительно. Так что удаленный конец, кажется, не имеет значения.

Кроме того, я попытался проверить примечание о «исключении предыдущего запуска предотвращает проблему только для следующего запуска», но у меня этого не произошло; Несмотря на это, я получаю такое поведение каждый раз.

Моар: Я удалил sleep чтобы посмотреть, что произойдет; Я все еще могу воспроизводить, хотя теперь это немного более прерывисто (хотя, поскольку это непросто воспроизвести в автоматическом цикле, все это воспроизводится вручную, что означает небольшое количество тестовых случаев, а это означает, что истинный% появления будет реальным трудно измерить точно.)

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

Интересно, связано ли это с pyinvoke / invoke # 552, который сводится к подклассу потока обработки исключений Invoke (используется здесь в ThreadingGroup), возможно, облажавшему обнаружение смерти потока.

Мне нужно убедиться, что я понимаю, что (его потенциальное исправление, pyinvoke / invoke # 553, не было мгновенным слиянием, так как казалось странным, что мы получили что-то явно функциональное, что так неправильно), а затем посмотреть, делает ли его применение этот симптом исчезнет.

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

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

Сегодня я заметил, что не могу воспроизвести поведение Exception, описанное месяц назад ... к сожалению, я не помню, что делал тогда. : /

Я действительно использую здесь bash. Хорошая находка. Тот факт, что проблема периодически возникает без сна, заставляет меня задаться вопросом, не является ли это каким-то состоянием гонки.

Вы так говорите, но теперь я не могу воспроизвести это снова, или, по крайней мере, это ОЧЕНЬ прерывисто. Если снова засыпать, он будет появляться намного чаще. Должен любить гоночные условия.

Глядя на эту проблему Invoke, репортер даже упоминает неисправный терминал как симптом; но, как ни странно, я не могу воспроизвести этот симптом даже в bash с его кодом. Тем не менее, не удивлюсь, если основная причина такая же (связана с некоторыми вещами, связанными с смертью потока и закрытием stdin, или, возможно, с возвращением к линейной буферизации, должным образом перед выходом).

Проверка пятен, упомянутых в другой проблеме, в отношении репродуктивного случая здесь:

  • бит ExceptionHandlingThread.is_dead , похоже, не имеет значения, он кажется правильным, что имеет некоторый смысл, поскольку он предназначен для обработки исключений в потоке, и ни один из этих случаев на самом деле не обрабатывает исключения. is_dead - это False для всех 3 рабочих потоков (stdin / out / err), когда я ожидал этого.
  • утверждение, что мы неправильно закрываем stdin подпроцесса, кажется более подходящим; если это оставит stdin управляющего терминала прикрепленным к теперь мертвому файловому дескриптору или чему-то еще ...? (Я действительно должен лучше знать, что происходит в этом случае.)

    • За исключением ... в случае Fabric, нет локального подпроцесса и нет прямой передачи файловых дескрипторов, так что это не может быть так.

    • Значит, проблема, скорее всего, в другом?


Пробуем другой прием ... что именно в терминальной среде после того, как ошибка обнаруживается, изменилась? При запуске stty -a под bash как с поврежденной ошибкой, так и без нее, я вижу следующие различия:

  • lflags : в неисправном терминале есть -icanon , -echo , -pendin (по сравнению с обычным термином, где все они не имеют знака минус). Отсутствие эха, безусловно, кажется проблемой, если предположить, что это означает именно это.
  • iflags : ошибка имеет -ixany и ignpar (первый пример того, что что-то установлено, а не сбрасывается, при плохой настройке)
  • oflags и cflags идентичны, как и cchars (я бы сильно расстроился, если бы управляющие символы изменились ...)

Согласно man stty :

  • icanon управляет обработкой ERASE и KILL; вероятно, не большая разница (может быть интересно, почему это установлено или не установлено)
  • echo - это то, на что это похоже, выводить ли эхо, и это, несомненно, самая большая практическая проблема ошибки.
  • pendin указывает, ожидает ли ввод (предполагая stdin) после канонического переключения (и поскольку icanon явно перевернут ... да) и будет ли он повторно введен, когда чтение станет ожидающим или будет больше ввода прибывает. Непонятно, почему это имеет значение, или почему он устанавливается нормально и не устанавливается при ошибке (во всяком случае, я ожидал последнего).
  • ixany позволяет любому символу «начать вывод» (а если не задано, разрешает только START. Ок?)
  • ignpar означает игнорировать (или не задавать, чтобы не игнорировать) символы с ошибками четности.

В целом создается впечатление, что к терминалу применяется некий «режим» более высокого уровня, аналогично тому, как мы устанавливаем stdin на чтение с символьной буферизацией, чтобы мы могли читать по 1 байту за раз, а не ждать, пока не войдет пользовательский пюре.

Это похоже на поведение при отображении (вроде ...), о котором я интересовался ранее; но при чтении рассматриваемого кода (поскольку этот патч Invoke также упоминает его, хотя и re: thread death), изменение режима выражается как контекстный менеджер, поэтому он _должен_ всегда сбрасываться, независимо от того, как мы выходим из этого цикла. Но сейчас мне нужно еще раз проверить это.

Незначительное: просто сказать stty echo для установки echo достаточно, чтобы "исправить" терминал; даже если icanon , pendin т. д. все еще не установлены. На самом деле это не помогает, но я думаю, это полезно знать.

OK! Думаю, я понял это, глядя на этот contextmanager: вероятно, потому, что contextmanager делает снимки текущего состояния терминала для восстановления в конце блока. Но что мы делаем в этом случае? Мы запускаем _два отдельных потока высокого уровня_, каждый из которых выполняет свою _собственную копию_ этого диспетчера контекста!

И в Invoke, хотя мы и намереваемся обеспечить потокобезопасность, в настоящее время мы не тестируем ничего, кроме наших собственных низкоуровневых потоков ввода-вывода; 99% «потокобезопасности» - это просто использование автономного состояния объекта вместо глобального состояния модуля horribad Fabric 1. Таким образом, этот конкретный бит сохранения состояния никогда не запускается одновременно с самим собой (отчасти потому, что «состояние» буквально является управляющим терминалом, из которых всегда только один, так что ... глобальное состояние ...).

Я еще не доказал это на 100% (собираюсь), но нет никакого способа, чтобы это было не так. Поток, который запускается вторым, с большой вероятностью сделает снимок управляющих атрибутов терминала, _после того, как_ первый поток уже установил его в режим символьного буфера; затем, если этот второй поток также _ завершает_ второй (опять же, вероятно, но не обязательно), он «восстанавливает» плохое состояние, эффективно отменяя восстановление первого потока.

Подтверждено, что флаг ECHO, например, определенно снимается не первым диспетчером контекста, а затем восстанавливается им. Работа над решением, которое, я думаю, в конечном итоге будет просто «попытаться выяснить, выглядит ли setcbreak уже примененным, и в этом случае нет операции вместо того, чтобы танцевать моментальный снимок-изменение-восстановление».

Должен иметь ожидаемый эффект, незначительно чище для загрузки (никогда не запускает setcbreak> 1 раз) и позволяет избежать углового случая, когда наивное исправление всегда может просто установить ECHO и т. Д. На «включено», что нарушит ситуацию, когда рассматриваемый поток tty-подобен, но _ уже_ настроен на отсутствие эха. (Вряд ли, конечно, но, вероятно, не невозможно.)

Поскольку это проблема только для Invoke, я собираюсь разместить ее на этом трекере - я ожидаю, что скоро получу тест и исправление для этого, но если у вас есть что-то еще, чтобы добавить, перейдите на https : //github.com/pyinvoke/invoke/issues/559

Чтобы быть предельно ясным, как только это будет исправлено, оно должно появиться в Invoke 1.0.2 / 1.1.1 (и, возможно, в 1.2.0, если я получу это одновременно), и должны быть необходимы обновления _no_ Fabric, только Invoke.

@bitprophet Отлично! Работает после обновления Invoke :)
Спасибо за ваши усилия.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги