Pythonモジュールの最上位レベルで実行されるコードによるBugの詳細な分析

2743 ワード

そしてInteractive Python promptでテストしました.

>>> 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されると、最上位のコードが実行する、様々な不可知なイベントが発生するからである.