如果在加载模块时使用 Paramiko,为什么它会挂起?
-
22-07-2019 - |
题
将以下内容放入文件中 你好.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 是如何知道它在模块初始化过程中被使用的呢?
解决方案
的paramiko使用单独的线程为底层传输。 你应该的从不的有产生一个线程作为进口的副作用的模块。据我了解,有一个单一的进口可用的锁,所以当从模块子线程尝试另一个导入,就可以无限期阻塞,因为你的主线程仍然持有锁。 (有一些我不知道的太多可能其他陷阱)
在一般情况下,模块不应该导入时有任何形式的副作用,或者你会得到不可预知的结果。牵住了与__name__ == '__main__'
招执行,你会没事的。
[编辑] 我似乎无法创建重现这一僵局一个简单的测试用例。我仍然认为这是一个线程问题与进口,因为该授权码正在等待永远不会触发一个事件。这可能是的paramiko,或Python的一个bug,但好消息是,你应该不会看到它,如果你做正确的事情;)
这是一个很好的例子,为什么你总是希望尽量减少副作用,以及为什么函数式编程技术正变得越来越普遍。
其他提示
作为 吉姆·B 指出这是一个 进口问题 当 python 尝试隐式导入时 str.decode('utf-8')
在 ssh 连接尝试期间首次使用时的解码器。看 分析 部分了解详细信息。
一般来说,无论怎样强调都应该避免让模块在导入时自动生成新线程。如果可以的话,尽量避免使用魔法模块代码,因为它几乎总是会导致不必要的副作用。
正如已经提到的,解决您的问题的简单而明智的方法是将您的代码放入
if __name__ == '__main__':
body 只在执行该特定模块时才会执行,而当该 mmodule 被其他模块导入时不会执行。(不推荐)另一个修复方法是在调用之前在代码中执行一个虚拟 str.decode('utf-8')
SSHClient.connect()
- 请参阅下面的分析。
那么这个问题的根本原因是什么呢?
分析(简单密码验证)
暗示:如果你想在 python import 和 set 中调试线程 threading._VERBOSE = True
paramiko.SSHClient().connect(.., look_for_keys=False, ..)
隐式地为您的连接生成一个新线程。如果您打开调试输出,您也可以看到这一点paramiko.transport
.
[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
这基本上是作为一部分完成的
SSHClient.connect()
. 。什么时候client.py:324::start_client()
被调用,创建一个锁transport.py:399::event=threading.Event()
并且线程已启动transport.py:400::self.start()
. 。请注意,start()
然后方法将执行类的transport.py:1565::run()
方法。transport.py:1580::self._log(..)
打印我们的日志消息“正在启动线程”,然后继续transport.py:1584::self._check_banner()
.check_banner
做一件事。它检索 ssh 横幅(来自服务器的第一个响应)transport.py:1707::self.packetizer.readline(timeout)
(请注意,超时只是一个套接字读取超时),在结尾处检查lineFeed,否则会出来。如果收到服务器横幅,它会尝试对响应字符串进行 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')
)
使固定
脏修复 - 不建议
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()
“” 解码( “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