$ ls ~yifei/notes/

python 中处理信号

Posted on:

Last modified:

昨天写了一个服务,在本地运行很好,使用 Ctrl-C 结束运行之后会清理资源,然后取消注册,然而 放到 Docker 中跑之后发现结束之后资源没有释放。查了查发现原来是下面是几个因素造成的:

  1. Ctrl-C 发送的是 SIGINT 信号,Python 会转化成 KeyboardInterrupt 异常,而我的资源是在 finally 释放资源,所以使用 Ctrl-C 可以优雅地退出
  2. 默认情况下,Python 中对其他的信号(比如 SIGTERM、SIGHUP)都不会处理,而是直接退出
  3. Docker 在退出的时候默认发送 SIGTERM 信号

所以在 docker stop 的时候服务会直接挂掉。和 docker 类似,systemctl 还会发送 SIGHUP 信号。

SIGTERM 解决方法

可以使用 signal 模块,注册一个 SIGTERM 的 handler,把 SIGTERM 转换成普通的异常就行啦, 这样在退出时也会运行 finally 子句中的语句。

import signal

def handler(signum, frame):
    raise KeyboardInterrupt

signal.signal(signal.SIGTERM, handler)
signal.signal(signal.SIGHUP, handler)

下面再介绍一下 signal 模块中的其他用法。

signal.signal(SIGXXX, handler)

为 SIGXXX 设置处理函数,handler 函数接收两个参数,signum 和 frame.

内置处理函数

signal 模块还内置了两个处理函数,可以用作 signal.signal 的参数。

signal.SIG_DFL,执行系统的默认处理函数。比如说,SIGQUIT 会导致 coredump 并退出,SIGCHLD 则会忽略,不做任何操作。

signal.SIG_IGN,直接忽略信号

signal.alarm

signal.alarm 可以使子线程有能力通知主线程退出,也可以给潜在的堵塞型操作一个退出的机会。

每个线程都可以执行 alarm, getsignal, pause, setitimergetitimer 函数,但是 只有主线程可以设置新的信号处理函数,也只有主线程会接受到信号。

import signal, os

def handler(signum, frame):
    print('Signal handler called with signal', signum)
    raise IOError("Couldn't open device!")

# 设置一个 5s 的 alarm,即使 hang 住,也会有机会跳出
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)

try:
    # 这个文件打开操作可能会无限 hang 住
    fd = os.open('/dev/ttyS0', os.O_RDWR)
except IOError:
    pass

signal.alarm(0)  # Disable the alarm

参考

  1. docker 退出信号
  2. finally 中的语句并不总会执行
  3. Python 不处理 SIGTERM 信号
  4. sys.exit 也会运行 finally 中的语句
  5. https://docs.python.org/3/library/signal.html
  6. https://en.wikipedia.org/wiki/SIGHUP
  7. https://stackoverflow.com/a/43712087/1061155
WeChat Qr Code

© 2016-2022 Yifei Kong. Powered by ynotes

All contents are under the CC-BY-NC-SA license, if not otherwise specified.

Opinions expressed here are solely my own and do not express the views or opinions of my employer.

友情链接: MySQL 教程站