溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python踩坑之旅其一殺不死的Shell子進(jìn)程

發(fā)布時(shí)間:2020-08-06 02:35:03 來源:網(wǎng)絡(luò) 閱讀:2126 作者:mythmgn 欄目:軟件技術(shù)

1.1 踩坑案例

踩坑的程序是個(gè)常駐的Agent類管理進(jìn)程, 包括但不限于如下類型的任務(wù)在執(zhí)行:

  • a. 多線程的網(wǎng)絡(luò)通信包處理
    • 和控制Master節(jié)點(diǎn)交互
    • 有固定Listen端口
  • b. 定期作業(yè)任務(wù), 通過subprocess.Pipe執(zhí)行shell命令
  • c. etc

發(fā)現(xiàn)坑的過程很有意思:

  • a.重啟Agent發(fā)現(xiàn)Port被占用了
    • => 立刻想到可能進(jìn)程沒被殺死, 是不是停止腳本出問題
    • => 排除發(fā)現(xiàn)不是, Agent進(jìn)程確實(shí)死亡了
    • => 通過 netstat -tanop|grep port_number 發(fā)現(xiàn)端口確實(shí)有人占用
    • => 調(diào)試環(huán)境, 直接殺掉占用進(jìn)程了之, 錯(cuò)失首次發(fā)現(xiàn)問題的機(jī)會
  • b.問題在一段時(shí)間后重現(xiàn), 重啟后Port還是被占用
    • 定位問題出現(xiàn)在一個(gè)叫做xxxxxx.sh的腳本, 該腳本占用了Agent使用的端口
    • => 奇了怪了, 一個(gè)xxx.sh腳本使用這個(gè)奇葩Port干啥(大于60000的Port, 有興趣的磚友可以想下為什么Agent默認(rèn)使用6W+的端口)
    • => review該腳本并沒有進(jìn)行端口監(jiān)聽的代碼
  • 一拍腦袋, c.進(jìn)程共享了父進(jìn)程資源
    • => 溯源該腳本,發(fā)現(xiàn)確實(shí)是Agent啟動的任務(wù)中的腳本之一
    • => 問題基本定位, 該腳本屬于Agent調(diào)用的腳本
    • => 該Agent繼承了Agent原來的資源FD, 也就是這個(gè)port
    • => 雖然該腳本由于超時(shí)被動觸發(fā)了terminate機(jī)制, 但terminate并沒有干掉這個(gè)子進(jìn)程
    • => 該腳本進(jìn)程的父進(jìn)程(ppid) 被重置為了1
  • d.問題**出在腳本進(jìn)程超時(shí)kill邏輯**

1.2 填坑解法

通過代碼review, 找到shell具體執(zhí)行的庫代碼如下:

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=True

1.3 坑位分析

Agent會在一個(gè)新創(chuàng)建的threading線程中執(zhí)行這段代碼, 如果線程執(zhí)行時(shí)間超時(shí)(xx seconds), 會調(diào)用 self._subpro.terminate()終止該腳本.

表面正常:

  • 啟用新線程執(zhí)行該腳本
  • 如果出現(xiàn)問題,執(zhí)行超時(shí)防止hang住其他任務(wù)執(zhí)行調(diào)用terminate殺死進(jìn)程

深層問題:

  • Python 2.7.x中subprocess.Pipe 如果shell=True, 會默認(rèn)把相關(guān)的pid設(shè)置為shell(sh/bash/etc)本身(執(zhí)行命令的shell父進(jìn)程), 并非執(zhí)行cmd任務(wù)的那個(gè)進(jìn)程
  • 子進(jìn)程由于會復(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ò)展知識

本節(jié)擴(kuò)展知識包括二個(gè)部分:

  • Linux系統(tǒng)中, 子進(jìn)程一般會繼承父進(jìn)程的哪些信息
  • Agent這種常駐進(jìn)程選擇>60000端口的意義

擴(kuò)展知識留到下篇末尾講述, 感興趣的可以自行搜索

1.4.1 技術(shù)關(guān)鍵字

  • Linux系統(tǒng)進(jìn)程
  • Linux隨機(jī)端口選擇
  • 程序多線程執(zhí)行
  • Shell執(zhí)行

1.5 填坑總結(jié)

  1. 子進(jìn)程會繼承父進(jìn)程的資源信息
  2. 如果只kill某進(jìn)程的父進(jìn)程, 集成了父進(jìn)程資源的子進(jìn)程會繼續(xù)占用父進(jìn)程的資源不釋放, 包括但不限于

    • listened port
    • opened fd
    • etc
  3. Python Popen使用上, shell的bool狀態(tài)決定了進(jìn)程kill的邏輯, 需要根據(jù)場景選擇使用方式

Life is short. We use Python

工號: 程序員的夢囈指南

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI