Python 之杀不死的Shell子进程
1.1 踩坑案例
踩坑的程序是個(gè)常駐的Agent類管理進(jìn)程, 包括但不限于如下類型的任務(wù)在執(zhí)行:
- a. 多線程的網(wǎng)絡(luò)通信包處理
- 和控制Master節(jié)點(diǎn)交互
- 有固定Listen端口
- b. 定期作業(yè)任務(wù), 通過(guò)subprocess.Pipe執(zhí)行shell命令
- c. etc
發(fā)現(xiàn)坑的過(guò)程很有意思:
-
a.重啟Agent發(fā)現(xiàn)Port被占用了
-
=> 立刻
想到可能進(jìn)程沒(méi)被殺死, 是不是停止腳本出問(wèn)題
- => 排除發(fā)現(xiàn)不是, Agent進(jìn)程確實(shí)死亡了
- => 通過(guò) netstat -tanop|grep port_number 發(fā)現(xiàn)端口確實(shí)有人占用
-
=> 調(diào)試環(huán)境, 直接殺掉占用進(jìn)程了之, 錯(cuò)失首次發(fā)現(xiàn)問(wèn)題的機(jī)會(huì)
-
-
b.問(wèn)題
在一段時(shí)間后
重現(xiàn)
, 重啟后Port還是被占用
- 定位問(wèn)題出現(xiàn)在一個(gè)叫做xxxxxx.sh的腳本, 該腳本占用了Agent使用的端口
- => 奇了怪了, 一個(gè)xxx.sh腳本使用這個(gè)奇葩Port干啥(大于60000的Port, 有興趣的磚友可以想下為什么Agent默認(rèn)使用6W+的端口)
- => review該腳本并沒(méi)有進(jìn)行端口監(jiān)聽(tīng)的代碼
- 定位問(wèn)題出現(xiàn)在一個(gè)叫做xxxxxx.sh的腳本, 該腳本占用了Agent使用的端口
-
一拍腦袋,
c.進(jìn)程共享了父進(jìn)程資源
了
- => 溯源該腳本,發(fā)現(xiàn)確實(shí)是Agent啟動(dòng)的任務(wù)中的腳本之一
- => 問(wèn)題基本定位, 該腳本屬于Agent調(diào)用的腳本
- => 該Agent繼承了Agent原來(lái)的資源FD, 也就是這個(gè)port
- => 雖然該腳本由于超時(shí)被動(dòng)觸發(fā)了terminate機(jī)制, 但terminate并沒(méi)有干掉這個(gè)子進(jìn)程
- => 該腳本進(jìn)程的父進(jìn)程(ppid) 被重置為了1
-
d.問(wèn)題出在腳本進(jìn)程超時(shí)kill邏輯
1.2 填坑解法
通過(guò)代碼review, 找到shell具體執(zhí)行的庫(kù)代碼如下:
''' 遇到問(wèn)題沒(méi)人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' self._subpro = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE,preexec_fn=_signal_handle ) # 重點(diǎn)是shell=True !把上述代碼改為:
self._subpro = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE,stderr=subprocess.PIPE, preexec_fn=_signal_handle ) # 重點(diǎn)是去掉了shell=True1.3 坑位分析
Agent會(huì)在一個(gè)新創(chuàng)建的threading線程中執(zhí)行這段代碼, 如果線程執(zhí)行時(shí)間超時(shí)(xx seconds), 會(huì)調(diào)用 self._subpro.terminate()終止該腳本.
表面正常:
- 啟用新線程執(zhí)行該腳本
- 如果出現(xiàn)問(wèn)題,執(zhí)行超時(shí)防止hang住其他任務(wù)執(zhí)行調(diào)用terminate殺死進(jìn)程
深層問(wèn)題:
- Python 2.7.x中subprocess.Pipe 如果shell=True, 會(huì)默認(rèn)把相關(guān)的pid設(shè)置為shell(sh/bash/etc)本身(執(zhí)行命令的shell父進(jìn)程), 并非執(zhí)行cmd任務(wù)的那個(gè)進(jìn)程
- 子進(jìn)程由于會(huì)復(fù)制父進(jìn)程的opened FD表, 導(dǎo)致即使被殺死, 依然保留了擁有這個(gè)Listened Port FD
這樣雖然殺死了shell進(jìn)程(未必死亡, 可能進(jìn)入defunct狀態(tài)), 但實(shí)際的執(zhí)行進(jìn)程確活著. 于是1.1中的坑就被結(jié)實(shí)的踩上了.
1.4 坑后擴(kuò)展
1.4.1 擴(kuò)展知識(shí)
本節(jié)擴(kuò)展知識(shí)包括二個(gè)部分:
- Linux系統(tǒng)中, 子進(jìn)程一般會(huì)繼承父進(jìn)程的哪些信息
- Agent這種常駐進(jìn)程選擇>60000端口的意義
擴(kuò)展知識(shí)留到下篇末尾講述, 感興趣的可以自行搜索
1.4.1 技術(shù)關(guān)鍵字
- Linux系統(tǒng)進(jìn)程
- Linux隨機(jī)端口選擇
- 程序多線程執(zhí)行
- Shell執(zhí)行
1.5 填坑總結(jié)
- listened port
- opened fd
- etc
總結(jié)
以上是生活随笔為你收集整理的Python 之杀不死的Shell子进程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python——有1、2、3、4个数字,
- 下一篇: python :如何将list存入txt