Python踩坑之旅其一杀不死的空壳子进程

  

1.1踩坑案例

  

踩坑的程序是个常驻的代理类管理进程,包括但不限于如下类型的任务在执行:

  
      <李>。多线程的网络通信包处理   
        <李>和控制主节点交互李   <李>有固定听端口   李
      <李> b。定期作业任务,通过subprocess.Pipe执行shell命令李   <李> c。等李   
  

发现坑的过程很有意思:

  
      <李> <>强。重启代理发现港口被占用了   
        <李>=比;立刻<强>想到可能进程没被杀的死,是不是停止脚本出问题   <李>=比;排除发现不是,代理进程确实死亡了李   <李>=比;通过<代码> netstat -tanop | grep port_number进行>   <李>=比;调试环境,<强>直接杀掉占用进程了之,错失首次发现问题的机会   李
      <李> <强> b。问题强在一段时间后<强>重现强,重启后港还是被占用   
        <李>定位问题出现在一个叫做xxxxxx。sh的脚本,该脚本占用了代理使用的端口   <李>=比;奇了怪了,一个xxx。sh脚本使用这个奇葩港口干啥(大于60000的端口,有兴趣的砖友可以想下为什么代理默认使用6 w +的端口)   <李>=比;审查<强>该脚本并没有进行端口监听的代码   李
      <李>一拍脑袋,<> c。进程共享了父进程资源强了   
        <李>=比;溯源该脚本,发现确实是代理启动的任务中的脚本之一李   <李>=比;问题基本定位,该脚本属于代理调用的脚本李   <李>=比;该代理继承了代理原来的资源FD,也就是这个港口李   <李>=比;虽然该脚本由于超时被动触发了终止机制,但终止并没有干掉这个子进程李   <李>=比;该脚本进程的父进程(ppid)被重置为1了李   李
      <李> <强> d。问题* * 强出在脚本进程超时杀死逻辑* *   
  

1.2填坑解法

  

通过代码审查,找到壳具体执行的库代码如下:

  
 <代码类="语言python ">自我。_subpro=subprocess.Popen (
  cmd, shell=True, stdout=subprocess.PIPE,
  stderr=subprocess.PIPE,
  preexec_fn=_signal_handle
  )
  #重点是shell=True !  
  

把上述代码改为:

  
 <代码类="语言python ">自我。_subpro=subprocess.Popen (
  cmd.split (), stdout=subprocess.PIPE,
  stderr=子流程。管、preexec_fn=_signal_handle
  )
  #重点是去掉了shell=True  
  

1.3坑位分析

  

代理会在一个新创建的螺纹线程中执行这段代码,如果线程执行时间超时(xx秒),会调用<代码> self._subpro.terminate() 终止该脚本。

  

表面正常:

  
      <李>启用新线程执行该脚本李   <李>如果出现问题,执行超时防止挂住其他任务执行调用终止杀死进程李   
  

深层问题:

  
      <李> Python 2.7.x中子流程。管如果壳=True,会默认把相关的pid设置为shell (sh/bash/等)本身(执行命令的壳父进程),并非执行cmd任务的那个进程李   <李>子进程由于会复制父进程的打开FD表,导致即使被杀死,依然保留了拥有这个港口FD听李   
  

这样虽然杀死进了壳程(未必死亡,可能进入已经状态),但实际的执行进程确活着。于是<代码> 中1.1的坑就被结实的踩上了。

  

1.4坑后扩展

  

1.4.1扩展知识

  

本节扩展知识包括二个部分:

  
      <李> Linux系统中,子进程一般会继承父进程的哪些信息李   <李>代理这种常驻进程选择祝辞60000端口的意义李   
  

扩展知识留到下篇末尾讲述,感兴趣的可以自行搜索

  

1.4.1技术关键字

  
      <李> Linux系统进程李   <李> Linux随机端口选择李   <李>程序多线程执行   <李>壳执行   
  

1.5填坑总结

  
      <李>子进程会继承父进程的资源信息李   <李>   

    如果只杀死某进程的父进程,集成了父进程资源的子进程会继续占用父进程的资源不释放,包括但不限于

      
        <李>听李> 打开fd李   <李>等李   
      李   <李> Python Popen使用上,壳牌的bool状态决定了进程杀死的逻辑,需要根据场景选择使用方式李   
  <人力资源/>   

生命是短暂的。我们使用Python

  

工号:程序员的梦呓指南

Python踩坑之旅其一杀不死的空壳子进程