Почему Paramiko зависает, если вы используете его при загрузке модуля? - PullRequest
10 голосов
/ 14 января 2009

Поместите в файл hello.py easy_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().

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

Ответы [ 3 ]

18 голосов
/ 16 января 2009

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

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

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

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

3 голосов
/ 01 октября 2015

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

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

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

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

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

Анализ (простой пароль аутентификации)

Подсказка: если вы хотите отладить многопоточность в импорте Python, установите 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) (обратите внимание, что тайм-аут - это просто тайм-аут чтения сокета), в конце проверяет перевод строки. и в противном случае время ожидания.

  4. В случае, если баннер сервера был получен, он пытается utf-8 декодировать строку ответа packet.py:287::return u(buf), и именно там происходит тупик. u(s, encoding='utf-8') выполняет код str. ('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 средство отслеживания проблем paramiko: выпуск 104

0 голосов
/ 05 апреля 2016

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...