Pythonモジュールの最上位レベルで実行されるコードによるBugの詳細な分析
2743 ワード
そしてInteractive Python promptでテストしました.
他のマシンで同じコードを実行すると、エラーが正しく放出されます.
サブプロセスが成功して終了したと勘違いしたためらしい.
深く分析する.
一見、この問題はPython自身やオペレーティングシステムによるものであるはずだ.これはいったいどうやって起こったのですか.そこで私の同僚はsubprocessのwait()方法を調べました.
os.waitpidのECHILD検出に失敗すると、エラーは投げ出されない.通常、1つのプロセスが終了すると、親プロセスがwait()メソッドを呼び出すまで、システムはその情報を記録し続ける.その間、このプロセスを「zombie」と呼ぶ.サブプロセスが存在しなければ、成功したか失敗したかは分からない.
以上のコードはまた別の問題を解決することができる:Pythonはデフォルトでサブプロセスが正常に終了したと考えている.多くの場合、この仮定は問題ない.しかし、プロセスがサブプロセスのSIGCHLDを無視することを明確に示すと、waitpid()は永遠に成功する.
元のコードに戻る
私たちは私たちのプログラムでSIGCHLDを無視することを明確に設定していますか?多くのサブプロセスを使用しているため、不可能ですが、ごく少数の場合にのみ同じ問題が発生します.git grepを使用すると、独立したコードの中でSIGCHLDを無視していることがわかりました.しかし、この世代はプログラムの一部ではなく、引用しただけだ.
1週間後
1週間後、この間違いは再び発生した.このエラーは、簡単なデバッグによりdebuggerで再現する.
いくつかのテストを経て、プログラムがSIGCHLDを無視したからこそ起こったこのバグを確定した.しかし、これはどのように起こったのでしょうか.
独立したコードを確認しました.
signal.Signal(signal.SIGCHLD,signal.SIG_IGN)私たちは何気なくこのコードをプログラムにimportしたのではないでしょうか.結果は私たちの推測が正しいことを示した.importがこのコードを使用すると、上記の文はfunctionではなくこのmoduleの最上位レベルにあるため、その実行を招き、SIGCHLDを無視し、サブプロセスエラーが投げ出されませんでした.
まとめ
このバグの発生は、私たちに2つの教訓を与えた.第一に、debugチェックでは、新しいコードから古いコード、Python Libraryに行くべきです.新しいコードでエラーが発生する確率は古いコードより大きいため、python libraryでエラーが発生する確率はより小さい.
第二に、副作用を引き起こす可能性のあるコードをmoduleの最上位層に書くのではなく、functuonに書くべきである.このmoduleがimportされると、最上位のコードが実行する、様々な不可知なイベントが発生するからである.
>>> import subprocess
>>> subprocess.check_call("false")
0
他のマシンで同じコードを実行すると、エラーが正しく放出されます.
>>> subprocess.check_call("false")
Traceback (most recent call last):
File "", line 1, in
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 542, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'false' returned non-zero exit status 1
サブプロセスが成功して終了したと勘違いしたためらしい.
深く分析する.
一見、この問題はPython自身やオペレーティングシステムによるものであるはずだ.これはいったいどうやって起こったのですか.そこで私の同僚はsubprocessのwait()方法を調べました.
def wait(self):
"""Wait for child process to terminate. Returns returncode attribute."""
while self.returncode is None:
try:
pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0)
except OSError as e:
if e.errno != errno.ECHILD:
raise
# This happens if SIGCLD is set to be ignored or waiting
# for child processes has otherwise been disabled for our
# process. This child is dead, we can't get the status.
pid = self.pid
sts = 0
# Check the pid and loop as waitpid has been known to return
# 0 even without WNOHANG in odd situations. issue14396.
if pid == self.pid:
self._handle_exitstatus(sts)
return self.returncode
os.waitpidのECHILD検出に失敗すると、エラーは投げ出されない.通常、1つのプロセスが終了すると、親プロセスがwait()メソッドを呼び出すまで、システムはその情報を記録し続ける.その間、このプロセスを「zombie」と呼ぶ.サブプロセスが存在しなければ、成功したか失敗したかは分からない.
以上のコードはまた別の問題を解決することができる:Pythonはデフォルトでサブプロセスが正常に終了したと考えている.多くの場合、この仮定は問題ない.しかし、プロセスがサブプロセスのSIGCHLDを無視することを明確に示すと、waitpid()は永遠に成功する.
元のコードに戻る
私たちは私たちのプログラムでSIGCHLDを無視することを明確に設定していますか?多くのサブプロセスを使用しているため、不可能ですが、ごく少数の場合にのみ同じ問題が発生します.git grepを使用すると、独立したコードの中でSIGCHLDを無視していることがわかりました.しかし、この世代はプログラムの一部ではなく、引用しただけだ.
1週間後
1週間後、この間違いは再び発生した.このエラーは、簡単なデバッグによりdebuggerで再現する.
いくつかのテストを経て、プログラムがSIGCHLDを無視したからこそ起こったこのバグを確定した.しかし、これはどのように起こったのでしょうか.
独立したコードを確認しました.
signal.Signal(signal.SIGCHLD,signal.SIG_IGN)私たちは何気なくこのコードをプログラムにimportしたのではないでしょうか.結果は私たちの推測が正しいことを示した.importがこのコードを使用すると、上記の文はfunctionではなくこのmoduleの最上位レベルにあるため、その実行を招き、SIGCHLDを無視し、サブプロセスエラーが投げ出されませんでした.
まとめ
このバグの発生は、私たちに2つの教訓を与えた.第一に、debugチェックでは、新しいコードから古いコード、Python Libraryに行くべきです.新しいコードでエラーが発生する確率は古いコードより大きいため、python libraryでエラーが発生する確率はより小さい.
第二に、副作用を引き起こす可能性のあるコードをmoduleの最上位層に書くのではなく、functuonに書くべきである.このmoduleがimportされると、最上位のコードが実行する、様々な不可知なイベントが発生するからである.