深入分析在Python模塊頂層運(yùn)行的代碼引起的一個(gè)Bug
然后我們?cè)贗nteractive Python prompt中測(cè)試了一下:
>>> import subprocess >>> subprocess.check_call("false") 0
而在其他機(jī)器運(yùn)行相同的代碼時(shí), 卻正確的拋出了錯(cuò)誤:
>>> 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
看來(lái)是subprecess誤以為子進(jìn)程成功的退出了導(dǎo)致的原因.
深入分析
第一眼看上去, 這一問(wèn)題應(yīng)該是Python自身或操作系統(tǒng)引起的. 這到底是怎么發(fā)生的? 于是我的同事查看了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
可見(jiàn), 如果os.waitpid的ECHILD檢測(cè)失敗, 那么錯(cuò)誤就不會(huì)被拋出. 通常, 當(dāng)一個(gè)進(jìn)程結(jié)束后, 系統(tǒng)會(huì)繼續(xù)記錄其信息, 直到母進(jìn)程調(diào)用wait()方法. 在此期間, 這一進(jìn)程就叫"zombie". 如果子進(jìn)程不存在, 那么我們就無(wú)法得知其是否成功還是失敗了.
以上代碼還能解決另外一個(gè)問(wèn)題: Python默認(rèn)認(rèn)為子進(jìn)程成功退出. 大多數(shù)情況下, 這一假設(shè)是沒(méi)問(wèn)題的. 但當(dāng)一個(gè)進(jìn)程明確表明忽略子進(jìn)程的SIGCHLD時(shí), waitpid()將永遠(yuǎn)是成功的.
回到原來(lái)的代碼中
我們是不是在我們的程序中明確設(shè)置忽略SIGCHLD? 不太可能, 因?yàn)槲覀兪褂昧舜罅康淖舆M(jìn)程, 但只有極少數(shù)情況下才出現(xiàn)同樣的問(wèn)題. 再使用git grep后, 我們發(fā)現(xiàn)只有在一段獨(dú)立代碼中, 我們忽略了SIGCHLD. 但這一代嗎根本就不是程序的一部分, 只是引用了一下.
一星期后
一星期后, 這一錯(cuò)誤又再一次發(fā)生. 并且通過(guò)簡(jiǎn)單的調(diào)試, 在debugger中重現(xiàn)了該錯(cuò)誤.
經(jīng)過(guò)一些測(cè)試, 我們確定了正是由于程序忽略了SIGCHLD才引起的這一bug. 但這是怎么發(fā)生的呢?
我們查看了那段獨(dú)立代碼, 其中有一段:
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
我們是不是無(wú)意間import了這段代碼到程序中? 結(jié)果顯示我們的猜測(cè)是正確的. 當(dāng)import了這段代碼后, 由于以上語(yǔ)句是在這一module的頂層, 而不是在一個(gè)function中, 導(dǎo)致了它的運(yùn)行, 忽略了SIGCHLD, 從而導(dǎo)致了子進(jìn)程錯(cuò)誤沒(méi)有被拋出!
總結(jié)
這一bug的發(fā)生, 給了我們兩個(gè)教訓(xùn). 第一是, 在debug檢查時(shí), 應(yīng)該從新的代碼到老的代碼, 再到Python Library. 因?yàn)樾麓a發(fā)生錯(cuò)誤的幾率大于老代碼, 而python library中發(fā)生錯(cuò)誤的幾率更小.
第二是, 不要將可能會(huì)引起副作用的代碼寫(xiě)在module頂層, 而應(yīng)當(dāng)寫(xiě)到functuon中. 因?yàn)槿绻搈odule被import, 那么在頂層的代碼就會(huì)運(yùn)行, 導(dǎo)致各種不可知的事件發(fā)生.
- Python運(yùn)行的17個(gè)時(shí)新手常見(jiàn)錯(cuò)誤小結(jié)
- Python 錯(cuò)誤和異常小結(jié)
- Python和Ruby中each循環(huán)引用變量問(wèn)題(一個(gè)隱秘BUG?)
- python錯(cuò)誤:AttributeError: ''module'' object has no attribute ''setdefaultencoding''問(wèn)題的解決方法
- 自己編程中遇到的Python錯(cuò)誤和解決方法匯總整理
- Python運(yùn)行報(bào)錯(cuò)UnicodeDecodeError的解決方法
相關(guān)文章
Python3中的最大整數(shù)和最大浮點(diǎn)數(shù)實(shí)例
今天小編就為大家分享一篇Python3中的最大整數(shù)和最大浮點(diǎn)數(shù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07對(duì)python產(chǎn)生隨機(jī)的二維數(shù)組實(shí)例詳解
今天小編就為大家分享一篇對(duì)python產(chǎn)生隨機(jī)的二維數(shù)組實(shí)例詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12Python反爬實(shí)戰(zhàn)掌握酷狗音樂(lè)排行榜加密規(guī)則
最新的酷狗音樂(lè)反爬來(lái)襲,本文介紹如何利用Python掌握酷狗排行榜加密規(guī)則,本章內(nèi)容只限學(xué)習(xí),切勿用作其他用途!?。。?! 有需要的朋友可以借鑒參考下2021-10-10Python抓新型冠狀病毒肺炎疫情數(shù)據(jù)并繪制全國(guó)疫情分布的代碼實(shí)例
在本篇文章里小編給大家整理了一篇關(guān)于Python抓新型冠狀病毒肺炎疫情數(shù)據(jù)并繪制全國(guó)疫情分布的代碼實(shí)例,有興趣的朋友們可以學(xué)習(xí)下。2020-02-02Python使用random和tertools模塊解一些經(jīng)典概率問(wèn)題
這篇文章主要介紹了Python使用random和tertools模塊解一些經(jīng)典概率問(wèn)題,本文講解了使用random和tertools模塊解羊車(chē)門(mén)問(wèn)題、撲克牌問(wèn)題、生日悖論等經(jīng)典概率問(wèn)題,需要的朋友可以參考下2015-01-01Python正則表達(dá)式函數(shù)match()和search()使用全面指南
在Python中,正則表達(dá)式是強(qiáng)大的工具,能夠用于文本匹配、搜索和替換,re模塊提供了許多函數(shù)來(lái)處理正則表達(dá)式,其中match()和search()是兩個(gè)常用的函數(shù),本文將深入探討這兩個(gè)函數(shù)的用法、區(qū)別和示例,幫助你更好地理解它們的功能2024-01-01