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

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的逻辑, 需要根据场景选择使用方式


Life is short. We use Python

工号: 程序员的梦呓指南

原文地址:https://blog.51cto.com/3011026/2405020

时间: 2024-10-01 19:39:40

Python踩坑之旅其一杀不死的Shell子进程的相关文章

Python 踩坑之旅进程篇其四一次性踩透 uid euid suid gid egid sgid的坑坑洼洼

目录 1.1 踩坑案例 1.2 填坑解法 1.3 坑位分析 1.4 技术关键字 1.5 坑后思考 下期坑位预告 代码示例支持 平台: Centos 6.3 Python: 2.7.14 代码示例: 菜单 - Python踩坑指南代码示例 1.1 踩坑案例 小明是个服务器管理员, 他从老管理员手里接手了一个非常繁琐的运维工作: 短暂授权root 账号给不同的 team 接口人运行备份任务 该运维任务有几个特点: 任务需且仅需运行在 root 下 root 账号只能短暂授权给各个小组 通过账号管理平

Zabbix 踩坑之旅——zabbix触发重启tomcat

一.实验需求 公司tomcat服务经常自动崩溃,导致业务中断,暂时用zabbix对其执行监控,在tomcat崩溃时能够先自动启动,保证业务尽快恢复正常. 二.准备环境 系统环境:CentOS 6.5 IP地址: zabbix-server: 192.168.239.128 zabbix-agent: 192.168.239.130 zabbix的服务端和客户端的安装此处都以rpm包安装,配置略过.agent端上安装好tomcat. 三.开启踩坑之旅--agent端 ① 修改zabbix-agen

vue+ vue-router + webpack 踩坑之旅

说是踩坑之旅 其实是最近在思考一些问题 然后想实现方案的时候,就慢慢的查到这些方案   老司机可以忽略下面的内容了 1)起因  考虑到数据分离的问题  因为server是express搭的   自然少不了res.render("xx",data)    这句话的意思就是去查找相应的模板文件然后在用数据去渲染在将渲染好的页面去返回给浏览器,给浏览器去解析,渲染模板其实就是做的替换字符串+拼接字符串的活  各种的模板引擎也有各个优化的点(比如可以将对应的模板编译的函数保存在内存中,然后在通

一次痛苦又甜蜜的微信支付踩坑之旅

凡是和钱打交道的事,没有一样是容易的.这是我第一次接触微信支付,发现网上还是有很多同学在求助,XXX了怎么办?XXX是什么情况?为了帮助更多的小伙伴脱离"苦海",我决定写下这次的踩坑之旅,给更多的人帮助. 介绍 微信支付方式分为刷卡支付.公众号支付.扫码支付.APP支付.H5支付.小程序支付. 先从应用场景来各自说一说,这样,能够最快的判断出应该选择哪一种支付. 刷卡支付:使用扫描设备(扫描枪)多见于超市.便利店使用 公众号支付:嵌入公众号的H5页面 扫码支付:用户打开"微信

小程序踩坑之旅

小程序踩坑之旅 —— 分包 小程序踩坑之旅 —— 分享 小程序踩坑之旅 —— 页面路由 小程序踩坑之旅 —— canvas 原文地址:https://www.cnblogs.com/xxhuan/p/11334792.html

python 3.6.1 安装scrapy踩坑之旅

系统环境:win10 64位系统安装 python基础环境配置不做过多的介绍 window环境安装scrapy需要依赖pywin32,下载对应python版本的exe文件执行安装,下载的pywin32版本不对安装会失败 下载依赖地址:https://sourceforge.net/projects/pywin32/files/pywin32/Build%20221/ 下载依赖安装完成后试下pip install scrapy,很显然失败了 发现网友的解决方案: 地址:https://blog.c

关于Python踩坑,a = "//" 之后 a is "//" 语句是否为True?

这两天踩了个坑,用写LCS的时候,发现 程序里面用了这个写法,然后就一直bug. 有点无语.常量的储存类型和变量的储存类型分开了. is 判断是比较的左右操作数的地址是否相等.如果是同一个对象,则返回True,否则返回False. 这里要特别注意,以后变量判断是否等于某一常量的时候,如果使用is,可能永远都是False. 但是遇到个坑, a = "+"? a is "+" a = "\\"? a is "\\" 两则后面一个语

百度地图sdk踩坑之旅

1.写在前面 项目中需要加上路线规划,导航,添加覆盖物,因为我最开始项目中定位我使用的是百度定位,所以为了省事,接着使用百度地图sdk实现这些.这两天踩了很多百度地图的坑,记下来.因为一些原因,后面会说,需求还没做完,所以效果图很简单,如下. 2.坑1,环境配置 对于百度地图sdk的配置我是无语.因为一开始项目中并没打算使用百度地图其他功能,只是准备定位.所以我下载sdk时只是下载了定位的sdk,如图 现在需要加上地图和导航的sdk(后面才知道导航并不需要下载专门的导航sdk,因为地图sdk可以

Python踩坑:类与类对象类型参数传递与使用

前言 对初学者来说,Python确实简单好用,毕竟动态类型语言,不用定义就可以拿来用,类型之间随意转换简直不要太方便,因此Python用来写写小脚本,爬虫程序什么的,没什么问题. 不过,一旦用来开发稍微大型一点的项目,例如搭建一个Web应用,就会遇到一些问题,一般缺乏经验的人都会陷入某些坑中.= =... 坑 先说坑,函数参数类型是一坑,类与类的对象这又是一坑. 虽然之前用其他静态类型语言(例如C#/Java)的时候都搞明白了的,但是换了个动态类型的Python,总会有点令人疑惑. 例子 让我用