在系统调用期间捕获/阻止SIGINT信号

8
我编写了一个网络爬虫,希望能够通过键盘停止它。我不想在中断时使程序终止;它需要先将数据刷新到磁盘上。同时,我也不想捕获KeyboardInterruptedException异常,因为持久数据可能处于不一致状态。
我的当前解决方案是定义一个信号处理程序来捕获SIGINT,并设置一个标志;每次主循环迭代处理下一个url之前都会检查这个标志。
然而,我发现如果系统在我发送中断时恰好正在执行socket.recv(),则会出现以下情况:
^C
Interrupted; stopping...  // indicates my interrupt handler ran
Traceback (most recent call last):
  File "crawler_test.py", line 154, in <module>
    main()
  ...
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/socket.py", line 397, in readline
    data = recv(1)
socket.error: [Errno 4] Interrupted system call

进程完全退出了。为什么会这样?我能否防止中断影响系统调用?

2个回答

9

socket.recv() 调用底层的 POSIX 兼容的 recv 函数,该函数在 C 层中执行。如果进程在等待接收数据时接收到 SIGINTrecv() 将返回错误码 EINTR。如果您使用 C 编程,可以在 C 端使用此错误码来检测 recv() 返回不是因为套接字上有更多可用数据,而是因为进程收到了 SIGINT。无论如何,Python 会将此错误码转换为异常,由于它从未被捕获,它会以回溯的形式终止您的应用程序。解决方法很简单,只需捕获 socket.error,检查错误码,如果等于 errno.EINTR,则悄悄地忽略异常。代码示例如下:

import errno

try:
    # do something
    result = conn.recv(bufsize)
except socket.error as (code, msg):
    if code != errno.EINTR:
        raise

1
在代码中使用数字4代替EINTR或Python提供的任何标识符是非常糟糕的做法。这很可能会在某些架构上出现错误。 - R.. GitHub STOP HELPING ICE
当然,你是对的。我再次阅读了Python库文档,看起来errno模块提供了这些常量,所以我会调整示例。 - Tamás
2
仅供参考:根据PEP-3151,此功能在Python 3.3中已被弃用。应改为使用OSError内置函数。 - Bunyk

3
如果您不希望套接字调用被中断,请在设置信号处理程序后禁用中断行为。
signal.signal(<your signal here>, <your signal handler function here>)
signal.siginterrupt(<your signal here>, False)

在信号处理函数中设置一些标志,例如一个线程事件(threading.Event()),然后在主处理函数中检查该标志并优雅地终止爬虫。
背景信息如下:
- Linux信号手册页面。请参见有关SA_RESTART标志的讨论。 - Python文档。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接