Python踩坑之旅其一殺不死的Shell子程式

mythmgn發表於2019-05-29

1.1 踩坑案例

踩坑的程式是個常駐的Agent類管理程式, 包括但不限於如下型別的任務在執行:

  • a. 多執行緒的網路通訊包處理
    • 和控制Master節點互動
    • 有固定Listen埠
  • b. 定期作業任務, 通過subprocess.Pipe執行shell命令
  • c. etc

發現坑的過程很有意思:

  • a.重啟Agent發現Port被佔用了
    • => 立刻想到可能程式沒被殺死, 是不是停止指令碼出問題
      • => 排除發現不是, Agent程式確實死亡了
      • => 通過 netstat -tanop|grep port_number 發現埠確實有人佔用
    • => 除錯環境, 直接殺掉佔用程式了之, 錯失首次發現問題的機會
  • b.問題在一段時間後重現, 重啟後Port還是被佔用
    • 定位問題出現在一個叫做xxxxxx.sh的指令碼, 該指令碼佔用了Agent使用的埠
      • => 奇了怪了, 一個xxx.sh指令碼使用這個奇葩Port幹啥(大於60000的Port, 有興趣的磚友可以想下為什麼Agent預設使用6W+的埠)
      • => review該指令碼並沒有進行埠監聽的程式碼
  • 一拍腦袋, c.程式共享了父程式資源
    • => 溯源該指令碼,發現確實是Agent啟動的任務中的指令碼之一
    • => 問題基本定位, 該指令碼屬於Agent呼叫的指令碼
    • => 該Agent繼承了Agent原來的資源FD, 也就是這個port
    • => 雖然該指令碼由於超時被動觸發了terminate機制, 但terminate並沒有幹掉這個子程式
    • => 該指令碼程式的父程式(ppid) 被重置為了1
  • d.問題出在指令碼程式超時kill邏輯

1.2 填坑解法

通過程式碼review, 找到shell具體執行的庫程式碼如下:

self._subpro = subprocess.Popen(
    cmd, shell=True, stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    preexec_fn=_signal_handle
)
# 重點是shell=True !

把上述程式碼改為:

self._subpro = subprocess.Popen(
    cmd.split(), stdout=subprocess.PIPE,
    stderr=subprocess.PIPE, preexec_fn=_signal_handle
)
# 重點是去掉了shell=True

1.3 坑位分析

Agent會在一個新建立的threading執行緒中執行這段程式碼, 如果執行緒執行時間超時(xx seconds), 會呼叫 self._subpro.terminate()終止該指令碼.

表面正常:

  • 啟用新執行緒執行該指令碼
  • 如果出現問題,執行超時防止hang住其他任務執行呼叫terminate殺死程式

深層問題:

  • Python 2.7.x中subprocess.Pipe 如果shell=True, 會預設把相關的pid設定為shell(sh/bash/etc)本身(執行命令的shell父程式), 並非執行cmd任務的那個程式
  • 子程式由於會複製父程式的opened FD表, 導致即使被殺死, 依然保留了擁有這個Listened Port FD

這樣雖然殺死了shell程式(未必死亡, 可能進入defunct狀態), 但實際的執行程式確活著. 於是1.1中的坑就被結實的踩上了.

1.4 坑後擴充套件

1.4.1 擴充套件知識

本節擴充套件知識包括二個部分:

  • Linux系統中, 子程式一般會繼承父程式的哪些資訊
  • Agent這種常駐程式選擇>60000埠的意義

擴充套件知識留到下篇末尾講述, 感興趣的可以自行搜尋

1.4.1 技術關鍵字

  • Linux系統程式
  • Linux隨機埠選擇
  • 程式多執行緒執行
  • Shell執行

1.5 填坑總結

  1. 子程式會繼承父程式的資源資訊
  2. 如果只kill某程式的父程式, 整合了父程式資源的子程式會繼續佔用父程式的資源不釋放, 包括但不限於

    • listened port
    • opened fd
    • etc
  3. Python Popen使用上, shell的bool狀態決定了程式kill的邏輯, 需要根據場景選擇使用方式

相關文章