Python多进程下信号处理的问题

"python多进程"

Posted by Keal on September 17, 2020

问题

我们开发的系统中, 客户反应说有时候FTP传片失败.

重点:

  1. 有时候
  2. FTP传输失败

排查

​ 通过日志和代码排查, 发现是在本地做内容复制的时候, 会触发一个SIGTERM信号, 主进程中注册的信号处理函数针对这个信号会做出退出程序的处理, 导致FTP服务挂掉, 传片自然也失败. (这里奇怪的点是, 处理函数有关闭web服务, 但是执行时失败了)

服务概况

​ 整个系统由一个web主进程, 一个由subprocess启动的FTP服务进程组成. 主进程中注册了处理的相关函数.

本地内容复制流程

​ 通过multiprocss fork一个子进程做复制动作, 新开一个Thread通过检测复制过程中内容是否传完来判断进程是否完成,完成之后调用terminate(), join()确保子进程关闭

问题原因

​ python的multiprocess中的terminate服务实际上是通过发送SIGTERM信号完成对进程的终止的. 而fork出来的子进程同样包含了主进程中处理信号的相关内容. 因此在收到SIGTERM信号时,触发了处理函数关闭系统.

其他问题
  1. 为何是有时候触发,即偶尔会触发
  2. 为何子进程中的SIGTERM处理函数没有完整的走完

​ 1问题猜测: 偶尔触发取决于terminate发送SIGTERM信号时, 子进程是否仍然存活. 因为本地复制的子进程是会在复制完成后,自动退出的,所以如果在terminate之前,子进程退出完成了,则无法触发此情景. 如果子进程在传输完成后,仍然进行了一些平时很快,但是某些情况下较慢的操作.(例如资源清理), 那么在检测复制内容是否结束的线程中执行terminate终止子进程时, 子进程在存活的情况下收到了这个SIGTERM信号, 开始了后续退出系统的流程

​ 2问题猜测:主要是web服务没有退出,查看代码是调用了socket的close接口, 而子进程中复制出来的socket其实跟主进程中的socket不是同一个, 所以主进程的socket依然存活, 外界依然可以访问此socket.

解决

​ 因为子进程并不是一个无限循环不会终止的进程,所以去掉了terminate的调用.通过join来保证子进程已经全部执行完成, 这样就不会发送信号来触发此错误场景.

总结

​ 排查这个问题的困难点在于:

  1. 测试没法百分百复现这个问题
  2. 日志只提到了信号的事情, 对信号相关的处理,包括linux内核处理和代码程序处理不够熟练
  3. 对fork子进程的一些细节知识,比如多线程下,调用fork函数, 并不会包含所有线程,而只包含调用fork函数的线程. fork出来的子进程是否能够处理SIGTERM信号等内容的不确定
  4. 对系统整体代码流程的不够熟悉
  5. 多线程下fork子进程的做法仍然有待考虑.
  6. terminate的调用不够谨慎, 在类似非无限循环的进程中使用terminate不够合理.

不过好在虽然细节不够熟练,但是各种知识能够有大致的了解, 最终没有走太多弯路.