Python 如何模拟终端CTRL+;来自单元测试的C事件?

Python 如何模拟终端CTRL+;来自单元测试的C事件?,python,unit-testing,signals,sigint,Python,Unit Testing,Signals,Sigint,我有一个多处理.Process子类,它忽略了SIGINT: # inside the run method signal.signal(signal.SIGINT, signal.SIG_IGN) 我不想在按下CTRL+C时终止此进程,因此我试图通过向此进程ID发送SIGINT信号来模拟unittests中的此终端事件: os.kill(PID, signal.SIGINT) 但即使不忽略此信号,进程也不会终止,因此此测试是无用的,我从其他问题中发现,在CTRL+C事件中,终端将SIGINT

我有一个
多处理.Process
子类,它忽略了
SIGINT

# inside the run method
signal.signal(signal.SIGINT, signal.SIG_IGN)
我不想在按下CTRL+C时终止此进程,因此我试图通过向此进程ID发送
SIGINT
信号来模拟unittests中的此终端事件:

os.kill(PID, signal.SIGINT)
但即使不忽略此信号,进程也不会终止,因此此测试是无用的,我从其他问题中发现,在CTRL+C事件中,终端将
SIGINT
发送到进程组ID,但在我的情况下,我不能这样做,因为它也会终止unittest进程


那么,为什么进程在从
os.kill
接收到
SIGINT
时不终止呢?我是否应该以另一种方式执行此操作?

子进程应该在收到SIGINT时终止,除非它忽略该信号或安装了自己的处理程序。如果没有在子级中显式忽略SIGINT,则可能在父级中忽略SIGINT,因此在子级中忽略SIGINT,因为信号配置是继承的

但是,我无法复制您的问题,事实上,我发现了相反的问题:无论其信号配置如何,子进程都会终止

如果在子进程忽略SIGINT(在其
run()
方法中)之前过早发送信号,它将被终止。下面是一些演示问题的代码:

import os, time, signal
from multiprocessing import Process

class P(Process):
    def run(self):
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        return super(P, self).run()

def f():
    print 'Child sleeping...'
    time.sleep(10)
    print 'Child done'

p = P(target=f)
p.start()    
print 'Child started with PID', p.pid

print 'Killing child'
os.kill(p.pid, signal.SIGINT)
print 'Joining child'
p.join()
输出

Child started with PID 1515 Killing child Joining child Traceback (most recent call last): File "p1.py", line 15, in p.start() File "/usr/lib64/python2.7/multiprocessing/process.py", line 130, in start self._popen = Popen(self) File "/usr/lib64/python2.7/multiprocessing/forking.py", line 126, in __init__ code = process_obj._bootstrap() File "/usr/lib64/python2.7/multiprocessing/process.py", line 242, in _bootstrap from . import util KeyboardInterrupt Child started with PID 1660 Killing child Joining child Child sleeping... Child done
另一种不需要延迟也不需要自定义
run()
方法的方法是将父级设置为忽略SIGINT,启动子级,然后恢复父级的原始SIGINT处理程序。由于信号配置是继承的,因此子级将从开始时忽略SIGINT:

import os, time, signal
from multiprocessing import Process

def f():
    print 'Child sleeping...'
    time.sleep(10)
    print 'Child done'

p = Process(target=f)
old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
p.start()    
signal.signal(signal.SIGINT, old_sigint)    # restore parent's handler
print 'Child started with PID', p.pid

print 'Killing child'
os.kill(p.pid, signal.SIGINT)
print 'Joining child'
p.join()
输出

Child started with PID 1515 Killing child Joining child Traceback (most recent call last): File "p1.py", line 15, in p.start() File "/usr/lib64/python2.7/multiprocessing/process.py", line 130, in start self._popen = Popen(self) File "/usr/lib64/python2.7/multiprocessing/forking.py", line 126, in __init__ code = process_obj._bootstrap() File "/usr/lib64/python2.7/multiprocessing/process.py", line 242, in _bootstrap from . import util KeyboardInterrupt Child started with PID 1660 Killing child Joining child Child sleeping... Child done Child从PID 1660开始 杀害儿童 加入儿童 孩子睡觉。。。 孩子做完了
该问题的简化版本为:

import os, time, signal

childpid = os.fork()

if childpid == 0:
    # in the child
    time.sleep(5)      # will be interrupted by KeyboardInterrupt
    print "stop child"
else:
    # in the parent
    #time.sleep(1)
    os.kill(childpid, signal.SIGINT)
如果父级在发送信号之前执行了
睡眠(1)
,则一切正常:子级(只有子级)接收到Python键盘中断异常,该异常会中断
睡眠(5)
。但是,如果我们像上面的例子那样注释掉
sleep(1)
,那么
kill()
似乎被完全忽略:孩子运行,睡眠5秒钟,最后打印
“stop child”
。因此,您的测试套件有一个简单的解决方法:只需添加一个小的
sleep()

据我所知,发生这种情况的原因如下(不好):查看CPython源代码,在系统调用fork()之后,子进程显式清除挂起信号列表。但以下情况似乎经常发生:父节点继续稍微领先于子节点,并发送SIGINT信号。子对象接收到它,但此时它仍然只是在系统调用fork()之后不久,在
\u clear\u pending\u signals()之前。因此,信号丢失


如果您想在上提交问题,这可能被视为CPython错误。参见signalmodule.c.中的PyOS_AfterFork()

您能粘贴一个代码示例吗?我天真地试着这么做,但效果如期:对不起,朋友,我现在说:@ArminRigo非常感谢,发送信号前后的睡眠解决了问题,似乎做得很快,你能不能把它作为一个答案贴出来,这样我就可以接受它。@Pierre:你能确认孩子在这两种情况下都没有被SIGINT终止,也就是说,孩子是否忽略了SIGINT吗?我发现正好相反:在这两种情况下,child都被终止。你提到的时间问题似乎证实了我的经验,因为孩子在忽略信号之前就收到了信号。在父级中添加一个小延迟可以修复它。@mhawke yes在这两种情况下,子级都不是由SIGINT终止的,我还尝试设置一个信号处理程序来检查子级是否接收到信号,并且它根本没有触发。发送信号前后的睡眠解决了这个问题。