Отправка ^ C в объекты подпроцесса Python в Windows - PullRequest
22 голосов
/ 17 августа 2011

У меня есть тестовая программа (написанная на Python), которая должна завершить тестируемую программу (написанную на C), отправив ее ^ C . В Unix

proc.send_signal(signal.SIGINT)

работает отлично. В Windows это выдает ошибку («сигнал 2 не поддерживается» или что-то в этом роде). Я использую Python 2.7 для Windows, поэтому у меня сложилось впечатление, что я смогу сделать вместо этого

proc.send_signal(signal.CTRL_C_EVENT)

но это ничего не делает. Что мне нужно сделать? Это код, который создает подпроцесс:

# Windows needs an extra argument passed to subprocess.Popen,
# but the constant isn't defined on Unix.
try: kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
except AttributeError: pass
proc = subprocess.Popen(argv,
                        stdin=open(os.path.devnull, "r"),
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        **kwargs)

Ответы [ 4 ]

12 голосов
/ 02 ноября 2011

Существует решение с использованием оболочки (как описано в ссылке, предоставленной Vinay), которая запускается в новом окне консоли с помощью команды Windows start .

Код оболочки:

#wrapper.py
import subprocess, time, signal, sys, os

def signal_handler(signal, frame):
  time.sleep(1)
  print 'Ctrl+C received in wrapper.py'

signal.signal(signal.SIGINT, signal_handler)
print "wrapper.py started"
subprocess.Popen("python demo.py")
time.sleep(3) #Replace with your IPC code here, which waits on a fire CTRL-C request
os.kill(signal.CTRL_C_EVENT, 0)

Код программы, перехватывающей CTRL-C:

#demo.py

import signal, sys, time

def signal_handler(signal, frame):
  print 'Ctrl+C received in demo.py'
  time.sleep(1)
  sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print 'demo.py started'
#signal.pause() # does not work under Windows
while(True):
  time.sleep(1)

Запустите оболочку, например, например:

PythonPrompt> import subprocess
PythonPrompt> subprocess.Popen("start python wrapper.py", shell=True)

Вам необходимо добавить код IPCкоторый позволяет вам управлять оболочкой, используя команду os.kill (signal.CTRL_C_EVENT, 0).Я использовал для этой цели сокеты в своем приложении.

Объяснение:

Предварительная информация

  • send_signal(CTRL_C_EVENT) не работает, поскольку CTRL_C_EVENTтолько для os.kill. [REF1]
  • os.kill(CTRL_C_EVENT) отправляет сигнал всем процессам, запущенным в текущем окне cmd [REF2]
  • Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) неработать, потому что CTRL_C_EVENT игнорируется для групп процессов. [REF2] Это ошибка в документации по питону [REF3]

Реализованное решение

  1. Дайте вашей программе работатьв другом окне cmd с помощью команды оболочки Windows start .
  2. Добавьте оболочку запроса CTRL-C между вашим управляющим приложением и приложением, которое должно получить сигнал CTRL-C.Оболочка будет работать в том же окне cmd, что и приложение, которое должно получить сигнал CTRL-C.
  3. Оболочка сама отключится, и программа, которая должна получить сигнал CTRL-C, отправит все процессы в cmd.окно CTRL_C_EVENT.
  4. Управляющая программа должна иметь возможность запрашивать оболочку для отправки сигнала CTRL-C.Это может быть реализовано через IPC, например, сокеты.

Полезные сообщения были:

Мне пришлось удалить http перед ссылками, потому что я новыйПользователь и не имеет права публиковать более двух ссылок.

Обновление: оболочка CTRL-C на основе IPC

Здесь вы можете найти самописный модуль python, обеспечивающий упаковку CTRL-C, включая IPC на основе сокетов.Синтаксис очень похож на модуль подпроцесса.

Использование:

>>> import winctrlc
>>> p1 = winctrlc.Popen("python demo.py")
>>> p2 = winctrlc.Popen("python demo.py")
>>> p3 = winctrlc.Popen("python demo.py")
>>> p2.send_ctrl_c()
>>> p1.send_ctrl_c()
>>> p3.send_ctrl_c()

Код

import socket
import subprocess
import time
import random
import signal, os, sys


class Popen:
  _port = random.randint(10000, 50000)
  _connection = ''

  def _start_ctrl_c_wrapper(self, cmd):
    cmd_str = "start \"\" python winctrlc.py "+"\""+cmd+"\""+" "+str(self._port)
    subprocess.Popen(cmd_str, shell=True)

  def _create_connection(self):
    self._connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self._connection.connect(('localhost', self._port))

  def send_ctrl_c(self):
    self._connection.send(Wrapper.TERMINATION_REQ)
    self._connection.close()

  def __init__(self, cmd):
    self._start_ctrl_c_wrapper(cmd)
    self._create_connection()


class Wrapper:
  TERMINATION_REQ = "Terminate with CTRL-C"

  def _create_connection(self, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('localhost', port))
    s.listen(1)
    conn, addr = s.accept()
    return conn

  def _wait_on_ctrl_c_request(self, conn):
    while True:
      data = conn.recv(1024)
      if data == self.TERMINATION_REQ:
        ctrl_c_received = True
        break
      else:
        ctrl_c_received = False
    return ctrl_c_received

  def _cleanup_and_fire_ctrl_c(self, conn):
    conn.close()
    os.kill(signal.CTRL_C_EVENT, 0)

  def _signal_handler(self, signal, frame):
    time.sleep(1)
    sys.exit(0)

  def __init__(self, cmd, port):
    signal.signal(signal.SIGINT, self._signal_handler)
    subprocess.Popen(cmd)
    conn = self._create_connection(port)
    ctrl_c_req_received = self._wait_on_ctrl_c_request(conn)
    if ctrl_c_req_received:
      self._cleanup_and_fire_ctrl_c(conn)
    else:
      sys.exit(0)


if __name__ == "__main__":
  command_string = sys.argv[1]
  port_no = int(sys.argv[2])
  Wrapper(command_string, port_no)
8 голосов
/ 17 августа 2011

Попробуйте вызвать функцию GenerateConsoleCtrlEvent, используя ctypes. Когда вы создаете новую группу процессов, идентификатор группы процессов должен совпадать с идентификатором pid. Итак, что-то вроде

import ctypes

ctypes.windll.kernel32.GenerateConsoleCtrlEvent(0, proc.pid) # 0 => Ctrl-C

должно работать.

Обновление: Вы правы, я пропустил эту часть детали. Вот пост , в котором предлагается возможное решение, хотя это немного глупо. Более подробная информация содержится в этом ответе .

2 голосов
/ 27 февраля 2019

Вот полностью рабочий пример, который не нуждается в каких-либо изменениях в целевом скрипте .

Это переопределяет модуль sitecustomize, поэтому он может не подходить для каждого сценария.Однако в этом случае вы можете использовать файл * .pth в пакетах сайтов для выполнения кода при запуске подпроцесса (см. https://nedbatchelder.com/blog/201001/running_code_at_python_startup.html).

Редактировать . Это работает только из коробки дляподпроцессы в Python. Другие процессы должны вручную вызывать SetConsoleCtrlHandler(NULL, FALSE).

main.py

import os
import signal
import subprocess
import sys
import time


def main():
    env = os.environ.copy()
    env['PYTHONPATH'] = '%s%s%s' % ('custom-site', os.pathsep,
                                    env.get('PYTHONPATH', ''))
    proc = subprocess.Popen(
        [sys.executable, 'sub.py'],
        env=env,
        creationflags=subprocess.CREATE_NEW_PROCESS_GROUP,
        )
    time.sleep(1)
    proc.send_signal(signal.CTRL_C_EVENT)
    proc.wait()


if __name__ == '__main__':
    main()

custom-site \ sitecustomize.py

import ctypes
import sys
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

if not kernel32.SetConsoleCtrlHandler(None, False):
    print('SetConsoleCtrlHandler Error: ', ctypes.get_last_error(),
          file=sys.stderr)

sub.py

import atexit
import time


def cleanup():
    print ('cleanup')

atexit.register(cleanup)


while True:
    time.sleep(1)
0 голосов
/ 18 июня 2012

Я пробовал это, но по какой-то причине Ctrl + Break работает, а Ctrl + C - нет.Таким образом, использование os.kill(signal.CTRL_C_EVENT, 0) не удается, но выполнение os.kill(signal.CTRL_C_EVENT, 1) работает.Мне сказали, что это как-то связано с тем, что владелец процесса создания является единственным, кто может передать ctrl c?Имеет ли это смысл?

Чтобы уточнить, при запуске fio вручную в командном окне он работает, как и ожидалось.Использование разрывов CTRL + BREAK без сохранения журнала, как ожидается, и CTRL + C завершает запись в файл также, как и ожидалось.Проблема, кажется, в сигнале для CTRL_C_EVENT.

Похоже, что это ошибка в Python, но, скорее, ошибка в Windows.Также еще одна вещь, у меня была запущена версия cygwin и там тоже работала отправка ctrl + c в python, но с другой стороны, мы там на самом деле не запускаем собственные окна.

пример:

import subprocess, time, signal, sys, os
command = '"C:\\Program Files\\fio\\fio.exe" --rw=randrw --bs=1M --numjobs=8 --iodepth=64 --direct=1 ' \
    '--sync=0 --ioengine=windowsaio --name=test --loops=10000 ' \
    '--size=99901800 --rwmixwrite=100 --do_verify=0 --filename=I\\:\\test ' \
    '--thread --output=C:\\output.txt'
def signal_handler(signal, frame):
  time.sleep(1)
  print 'Ctrl+C received in wrapper.py'

signal.signal(signal.SIGINT, signal_handler)
print 'command Starting'
subprocess.Popen(command)
print 'command started'
time.sleep(15) 
print 'Timeout Completed'
os.kill(signal.CTRL_C_EVENT, 0)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...