python 杀死子进程_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 填坑总结

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

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

listened port

opened fd

etc

Python Popen使用上, shell的bool状态决定了进程kill的逻辑, 需要根据场景选择使用方式

建议大家也看一下这篇文章的姊妹篇, 此篇是子孙进程无法 kill/杀死的终极解法 [Python 踩坑之旅进程篇其三pgid是个什么鬼 (子进程\子孙进程无法kill 退出的解法)] (https://www.cnblogs.com/mythmgn/p/10945941.html)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,我们可以使用multiprocessing模块来创建和管理子进程。要杀死子进程,可以使用子进程对象的terminate()方法。根据引用和引用的示例代码,我们可以看到创建子进程的方式。 例如,假设我们有一个名为p的子进程对象,并且希望杀死该进程,我们可以使用以下代码: ```python p.terminate() ``` 这将发送一个终止信号给子进程,导致子进程立即终止。需要注意的是,这是一种强制终止的方式,不会给子进程执行清理操作的机会。因此,在使用terminate()方法之前,应该仔细考虑是否真的需要终止子进程。 希望这个回答对你有帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python多进程并发(multiprocessing)用法实例详解](https://download.csdn.net/download/weixin_38619207/12876946)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [如果父项在Python中被杀死,则杀死子进程](https://blog.csdn.net/weixin_39681058/article/details/118818558)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值