[python] os.kill, послать ctrl-C дочернему процессу реально?

Phoenix

Есть некоторый сервер. Сервер слушает порты, что-то отправляет клиентам. Завершение работы проиходит по ctrl-C.
Сейчас пишу для этого сервера тесты. Но никак не могу сэмулировать нажатие ctrl-C под виндой.
специально перешёл с 2.6 на 2.7, там появился os.kill для винды и специальные сигналы.
запускал дочерний процесс через multiprocessing и subprocess - ошибка одна и та же.
Если посылать какой угодно сигнал кроме CTRL_C_EVENT и CTRL_BREAK_EVENT - приложение помирает, не обработав сигнал.
Как работает subproc:
  
F:\igor\apps\devel\python\subproc>python sigtest.py subproc
['sigtest.py', 'subproc']
>>>>
**
**
**
**
**
**
**
**
**
sig int (2, <frame object at 0x00B2BCF8>)
  

Как работает запускающий процесс: (killing - послали ctrl-C, killing2 - послали ctrl-break)
 
F:\igor\apps\devel\python\subproc>python sigtest.py
['sigtest.py']
['python', 'sigtest.py', 'subproc']
['sigtest.py', 'subproc']
>>>>
**
killing
killed
**
**
**
**
**
**
**
**
**
killing 2
killed 2
signal (21, <frame object at 0x00AC0F98>)
  


 
 
# -*- encoding: utf-8 -*-

import signal
import os
import math
import sys
import subprocess
from time import sleep

def test_proc:
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGBREAK, signal_handler)

print '>>>>'
for i in xrange(10**2):
print '**'
for j in xrange(10**6):
a = math.sin(i)
print '>>>>'


def signal_handler(*args):
print 'signal', args
sys.exit(0)

def start_arbitr2(args):
pass
#start
args = ['python', args[0], 'subproc']
print args
pr = subprocess.Popen(args, stdin = None, stdout = None, stderr = subprocess.STDOUT, startupinfo=None, creationflags = subprocess.CREATE_NEW_PROCESS_GROUP)
sleep(0.5)
print 'killing'
pr.send_signal(signal.CTRL_C_EVENT)
print 'killed'
sleep(5)
print 'killing 2'
pr.send_signal(signal.CTRL_BREAK_EVENT)
print 'killed 2'

if __name__ == '__main__':
print sys.argv
if len(sys.argv) > 1 and sys.argv[1] == 'subproc':
test_proc
else:
#start_arbitr
start_arbitr2(sys.argv)


Заходим сюда и читаем
 
CREATE_NEW_PROCESS_GROUP
0x00000200
The new process is the root process of a new process group. The process group includes all processes that are descendants of this root process. The process identifier of the new process group is the same as the process identifier, which is returned in the lpProcessInformation parameter. Process groups are used by the GenerateConsoleCtrlEvent function to enable sending a CTRL+BREAK signal to a group of console processes.
If this flag is specified, CTRL+C signals will be disabled for all processes within the new process group.
This flag is ignored if specified with CREATE_NEW_CONSOLE.

А в доке по Popen:
 
Note
On Windows, SIGTERM is an alias for terminate. CTRL_C_EVENT and CTRL_BREAK_EVENT can be sent to processes started with a creationflags parameter which includes CREATE_NEW_PROCESS_GROUP.
  

И как послать тогда CTRL_C_EVENT? и зачем он вообще тогда нужен, если он игнорируется в том слуае, когда указываешь флаг CREATE_NEW_PROCESS_GROUP, а когда не указываешь - его послать не дают с ошибкой
WindowsError: [Error 87] The parameter is incorrect

Dasar

afaik, ctrl-c под виндой - это символ с кодом (лично не проверял)

Phoenix

хм. кажись про это знают, судя по
http://svn.python.org/view/python/branches/release27-maint/L...
784 	@unittest.skip("subprocesses aren't inheriting CTRL+C property")
785 def test_CTRL_C_EVENT(self):

чё делать? :grin:

Phoenix

Не работает.
 
# -*- encoding: utf-8 -*-

import signal
import os
import math
import sys
import subprocess
from time import sleep

def test_proc:
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGBREAK, signal_handler)

print '>>>>'
for i in xrange(10**2):
print '**'
for j in xrange(10**6):
a = math.sin(i)
print '>>>>'


def signal_handler(*args):
print 'signal', args
sys.exit(0)

def start_arbitr2(args):
pass
#start
args = ['python', args[0], 'subproc']
print args
pr = subprocess.Popen(args, stdin = subprocess.PIPE, stdout = None,
stderr = subprocess.STDOUT, startupinfo=None,
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP)
sleep(0.5)
print 'killing'
pr.send_signal(signal.CTRL_C_EVENT)
print 'killed'
sleep(1)
print 'killing 3'
#pr.send_signal(signal.CTRL_C_EVENT)
pr.stdin.write(chr(3
pr.stdin.flush
print 'killed 3 '
sleep(5)
print 'killing 2'
pr.send_signal(signal.CTRL_BREAK_EVENT)
print 'killed 2'

if __name__ == '__main__':
print sys.argv
if len(sys.argv) > 1 and sys.argv[1] == 'subproc':
test_proc
else:
#start_arbitr
start_arbitr2(sys.argv)

вывод
 
F:\igor\apps\devel\python\subproc>python sigtest.py
['sigtest.py']
['python', 'sigtest.py', 'subproc']
['sigtest.py', 'subproc']
>>>>
**
killing
killed
**
**
killing 3
killed 3
**
**
**
**
**
**
**
**
**
killing 2
killed 2

Dasar

есть еще функция GenerateConsoleCtrlEvent
http://msdn.microsoft.com/en-us/library/ms683155%28v=vs.85%2...
но хз как ты ее будешь из python-а вызывать

Phoenix

багофич растёт из виндовой GenerateConsoleCtrlEvent :lol:
http://msdn.microsoft.com/en-us/library/ms683155%28v=vs.85%2...
Интересно, а зачем вот так сделали?

Phoenix

:) Да вызвать не проблема. Она в os.kill и вызывается.
Можно, конечно, исхитриться и послать на консоль ctrl-c, но в управляющем окне проигнорировать или ещё какой костыль соорудить. :(

Dasar

>багофич растёт из виндовой GenerateConsoleCtrlEvent :lol:
какой именно багофич?

Phoenix

http://msdn.microsoft.com/en-us/library/ms683155%28v=vs.85%2...
This signal cannot be generated for process groups. If dwProcessGroupId is nonzero, this function will succeed, but the CTRL+C signal will not be received by processes within the specified process group.

и
http://msdn.microsoft.com/en-us/library/ms684863%28v=vs.85%2...
If this flag is specified, CTRL+C signals will be disabled for all processes within the new process group.

Вот зачем они игнорируют ctrl-c ?
в питоне сделали обязательным флаг CREATE_NEW_PROCESS_GROUP для нового процесса, видимо, для того, чтобы симулировать получение сигнала только одним процессом.

Dasar

>Вот зачем они игнорируют ctrl-c ?
скорее всего какую-нибудь свою функциональность через это протаскивали.
зы
более менее прямой путь получается: создается промежуточный процесс с new_process_group, который создает конечный процесс без new_process_group
когда надо промежуточный процесс посылает GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)
ззы
есть еще вариант - найти консольное окно, которое соответствует данному процессу, и послать ему ctrl-c

Phoenix

более менее прямой путь получается: создается промежуточный процесс с new_process_group, который создает конечный процесс без new_process_group
когда надо промежуточный процесс посылает GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)

тоже так думал делать,если ничего лучшего не придумать.
есть еще вариант - найти консольное окно, которое соответствует данному процессу, и послать ему ctrl-c

Это как? нет никаких проблем создать процесс в новом окне с CREATE_NEW_CONSOLE.

Phoenix

хехе. Нужно в дочернем процесс выполнить
SetConsoleCtrlHandler(NULL, 0)

и теперь он уже ничего не игнорирует.
ура!
Это было в тесте ( тут но там они это делают в родительском процессе. Видать поэтому у них и не работает.

Dasar

>Это как? нет никаких проблем создать процесс в новом окне с CREATE_NEW_CONSOLE.
через FindWindow - это консольное окно можно найти
идея здесь, но реализована грязновато
http://stackoverflow.com/questions/2274668/how-to-send-keys-...

bleyman

Ты, кстати, в курсе, что в венде и в юнихах сигналы работают принципиально разным образом — в юнихах вызывается хэндлер в том же треде, в венде — в отдельном? Ну, типа, ты вроде нашёл решение, но вдруг тебе это окажется полезным!

Phoenix

ты не про .net ?
То, что я соорудил ещё и мультитредовое получилось? не критично конечно. Я им пользуюсь только для завершения, так что не очень критично, хотя и работает благодаря GIL, который вроде как не гарантирован.
Вроде threding.current_thread.ident у них одинаковый(у основного цикла и хендлера для сигнала) и выполнение останавливается, когда сигнал посылается.
Хотя processexplorer показывает ещё один
ntdll.dll!Rt|SetLastWin32ErrorAndNtStatusFromNtStatus+0x59 - это что такое?
PS. его кильнуть можно и всё продолжает работать.
Оставить комментарий
Имя или ник:
Комментарий: