Почему Paramiko зависает, если вы используете его при загрузке модуля?

поместите следующее в файл hello.pyeasy_install paramiko если у вас этого нет):

hostname,username,password='fill','these','in'
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()

заполните первую строку соответствующим образом.

теперь типа

python hello.py

и вы увидите некоторые выходные данные ls.

теперь вместо типа

python

, а затем из типа переводчика

import hello

и вуаля! Он висит! Это unhang если вы обернуть код в функцию foo и import hello; hello.foo() вместо.

почему Paramiko зависает при использовании в инициализации модуля? как Парамико вообще знает, что он используется во время инициализации модуля в первую очередь?

3 ответов


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

вообще, модули не должны иметь побочные эффекты любой вид при импорте, или вы получите непредсказуемые результаты. Просто отложите исполнение с помощью __name__ == '__main__' трюк, и все будет хорошо.

[редактирование] Кажется, я не могу создать простой тестовый случай, который воспроизводит этот тупик. Я все еще предполагаю, что это проблема с потоком с импортом, потому что код аутентификации ждет события, которое никогда не срабатывает. Это может быть ошибка в paramiko или python, но хорошей новостью является то, что вы никогда не должны ее видеть, если делаете все правильно ;)

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


As JimB указал, что это вопрос когда python пытается неявно импортировать str.decode('utf-8') декодер при первом использовании во время попытки подключения ssh. См.анализ для сведения.

в общем, нельзя подчеркнуть достаточно, что вы должны избегать модуля, автоматически порождающего новые потоки при импорте. Если вы можете, попробуйте избежать кода magic module в целом, поскольку он почти всегда приводит к нежелательным побочные явления.

  1. простое и разумное решение вашей проблемы - как уже упоминалось-это поместить ваш код в if __name__ == '__main__': тело, которое будет выполняться только при выполнении этого конкретного модуля и не будет выполняться, когда этот mmodule импортируется другими модулями.

  2. (не рекомендуется) другое исправление-просто сделать фиктивный str.декодируйте ('utf-8') в своем коде перед вызовом SSHClient.connect() - см. Анализ под.

так в чем же коренная причина этой проблемы?

анализ (простой пароль)

подсказка: если вы хотите отладить поток в Python import и set threading._VERBOSE = True

  1. paramiko.SSHClient().connect(.., look_for_keys=False, ..) неявно порождает новый поток для подключения. Вы также можете увидеть это, если вы включите отладочный вывод для paramiko.transport.

[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L

  1. этот в основном, в составе SSHClient.connect(). Когда client.py:324::start_client() вызывается, создается блокировка transport.py:399::event=threading.Event() и поток начал transport.py:400::self.start(). Обратите внимание, что start() метод затем выполнит класс transport.py:1565::run() метод.

  2. transport.py:1580::self._log(..) печатает сообщение нашего журнала "начальный поток", а затем переходит к transport.py:1584::self._check_banner().

  3. check_banner делает одну вещь. Он извлекает SSH-баннер (первый ответ от сервера) transport.py:1707::self.packetizer.readline(timeout) (обратите внимание, что тайм-аут-это просто тайм-аут чтения сокета), проверяет наличие linefeed в конце и иное время.

  4. в случае получения баннера сервера, он пытается utf-8 декодировать строку ответа packet.py:287::return u(buf) и вот где затор происходит. The u(s, encoding='utf-8') делает str.декодировать ('utf-i') и неявно импортировать encodings.utf8 на encodings:99 via encodings.search_function в конечном итоге в тупик импорта.

таким образом, грязным исправлением было бы просто импортировать декодер utf-8 один раз, чтобы не блокируйте этот импорт specifiy из-за побочных эффектов импорта модуля. (''.decode('utf-8'))

исправить

dirty fix - не рекомендуется

import paramiko
hostname,username,password='fill','these','in'
''.decode('utf-8')  # dirty fix
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()

определить

import paramiko
if __name__ == '__main__':
    hostname,username,password='fill','these','in'
    c = paramiko.SSHClient()
    c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    c.connect(hostname=hostname, username=username, password=password)
    i,o,e = c.exec_command('ls /')
    print(o.read())
    c.close()

ref paramiko issue tracker: выпуск 104


"".декодирование ("utf-8") не сработало для меня, я закончил это делать.

from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")

У меня есть обертка для paramiko с этим реализованным. https://github.com/bucknerns/sshaolin