2015-08-15 2 views
1

У меня есть multiprocessing.Process подкласс, который пренебрегает SIGINT:Как имитировать терминал CTRL + C событие от unittest?

# inside the run method 
signal.signal(signal.SIGINT, signal.SIG_IGN) 

Я не хочу этот процесс прекращается при нажатии CTRL +C, так что я пытаюсь имитировать этот терминальный событие в моей UnitTests путем отправки SIGINT сигнала этого процесса ID:

os.kill(PID, signal.SIGINT) 

Но даже без игнорирования этого сигнала процесс не завершающий, поэтому этот тест бесполезно, я обнаружил, НУ t от других вопросов, которые на CTRL + C Событие, которое терминал отправляет SIGINT в идентификатор группы процессов, но я не могу сделать это в моем случае, так как он также завершит процесс unittest.

Итак, почему процесс не прекращается, когда он получает SIGINT от os.kill? и должен ли я делать это по-другому?

+1

Можете ли вы вставить пример кода? Я попытался сделать это наивно, но он работает так, как ожидалось: https://bpaste.net/show/f2c5fc34b8b6 –

+0

Извините друг i founf сейчас: http://stackoverflow.com/questions/13341870/signals-and-interrupts-a- сравнение – dsgdfg

+0

@ArminRigo большое спасибо, сон до и после отправки сигнала решает проблему, кажется, что это делается очень быстро, можете ли вы отправить его как ответ, чтобы я мог его принять. – Pierre

ответ

2

упрощенная версия этого вопроса является:

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) 

Если родитель делает sleep(1) перед отправкой сигнала, все работает так, как ожидалось: ребенок (и только ребенок) получает Python Keyboar d Исключение прерывания, которое прерывает sleep(5). Однако, если мы прокомментируем sleep(1), как в приведенном выше примере, kill() выглядит полностью игнорированным: ребенок запускается, спит 5 секунд и, наконец, печатает "stop child". Поэтому для вашего тестового набора возможно простое обходное решение: просто добавьте маленький sleep().

Насколько я понимаю, это происходит по следующей (плохой) причине: глядя на исходный код CPython, после системного вызова fork() дочерний процесс явно очищает список ожидающих сигналов. Но, как представляется, часто возникает следующая ситуация: родительский элемент немного впереди ребенка и посылает сигнал SIGINT. Ребенок получает его, но в этот момент он остается только вскоре после системного вызова fork() и до _clear_pending_signals(). В результате сигнал теряется.

Это может считаться ошибкой CPython, если вы хотите подать вопрос на http://bugs.python.org. См. PyOS_AfterFork() в signalmodule.c.

3

Детский процесс должен заканчиваться при получении 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 

Добавление небольшую задержку с time.sleep(0.1) в родителю непосредственно перед отправкой сигнала SIGINT ребенку будет решить эту проблему. Это даст ребенку достаточно времени для выполнения метода run(), в котором SIGINT игнорируется. Теперь сигнал будет игнорироваться ребенком:

 
Child started with PID 1589 
Killing child 
Child sleeping... 
Joining child 
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 1660 
Killing child 
Joining child 
Child sleeping... 
Child done 
Смежные вопросы