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

Поместите следующее в файл 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

и вуаля! Он висит! Он отключится, если вы оберните код в функцию foo и сделайте import hello; hello.foo() вместо этого.

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

  • (не рекомендуется) Еще одно исправление заключается в том, чтобы просто сделать dummy str.decode('utf-8') в вашем коде, прежде чем вы вызовете SSHClient.connect() - см. анализ ниже.

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

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

Совет. Если вы хотите отлаживать потоки в импорте python и устанавливать threading._VERBOSE = True

  • 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) (обратите внимание, что тайм-аут - это только тайм-аут чтения сокета), проверяет соответствие строки в конце и в противном случае истекает время.

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

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

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 paramico issue tracker: проблема 104

Ответ 3

". decode (" 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