モジュールのロード中に Paramiko を使用するとハングするのはなぜですか?
-
22-07-2019 - |
質問
以下をファイルに入れます こんにちは。 (そして 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 は、基礎となるトランスポートに別のスレッドを使用します。あなたがすべき 一度もない インポートの副作用としてスレッドを生成するモジュールがあります。私の理解では、使用可能なインポート ロックは 1 つだけであるため、モジュールの子スレッドが別のインポートを試行すると、メイン スレッドがまだロックを保持しているため、無限にブロックされる可能性があります。(私が気づいていない他にも注意点があるかもしれません)
一般に、モジュールにはインポート時にいかなる種類の副作用もあってはなりません。副作用が発生すると、予期しない結果が発生します。で実行を保留するだけです __name__ == '__main__'
トリックすれば大丈夫です。
編集]このデッドロックを再現する簡単なテストケースを作成できないようです。認証コードは決して起動しないイベントを待っているため、インポートのスレッドの問題だと私はまだ思っています。これは paramiko または Python のバグである可能性がありますが、良いニュースは、正しく処理していれば決してこのバグが発生するはずがないということです ;)
これは、副作用を常に最小限に抑えたい理由と、関数型プログラミング手法がより普及している理由の良い例です。
他のヒント
JimB が指摘したように、Pythonが暗黙的にインポートしようとするとインポートの問題 ssh接続試行中の最初の使用時の str.decode( 'utf-8')
デコーダー。詳細については、分析セクションを参照してください。
一般に、インポート時にモジュールが自動的に新しいスレッドを生成することを避ける必要があるほど十分に強調することはできません。可能であれば、マジックモジュールのコードは一般的に避けてください。ほとんどの場合、望ましくない副作用が発生します。
-
問題の簡単な-正気の修正-既に述べたように、コードを
if __name__ == '__main __':
本体に入れることは、次の場合にのみ実行されます。この特定のモジュールを実行し、このmmoduleが他のモジュールによってインポートされるときに実行されません。 -
(非推奨)別の修正方法は、
SSHClient.connect()
を呼び出す前にコード内でダミーのstr.decode( 'utf-8')を実行することです-以下の分析を参照。
では、この問題の根本原因は何ですか?
分析(単純なパスワード認証)
ヒント:Pythonインポートでスレッドをデバッグし、 threading._VERBOSE = True
-
paramiko.SSHClient()。connect(..、look_for_keys = False、..)
は、接続用の新しいスレッドを暗黙的に生成します。paramiko.transport
のデバッグ出力をオンにした場合にも、これを確認できます。
[Thread-5] [paramiko.transport] DEBUG:スレッドの開始(クライアントモード):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
は1つのことを行います。 sshバナー(サーバーからの最初の応答)transport.py:1707::self.packetizer.readline(timeout)
(タイムアウトは単なるソケット読み取りタイムアウトであることに注意してください)を取得し、改行をチェックします最後に それ以外の場合はタイムアウトします。 -
サーバーバナーが受信された場合、応答文字列
packet.py:287::return u(buf)
のutf-8デコードを試行し、デッドロックが発生します。u(s、encoding = 'utf-8')
はstr.decode( 'utf-i')を実行し、encodingsの
をencodings.utf8
を暗黙的にインポートします:99encodings.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()
"" .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