Windows: Как Jupyter генерирует прерывание клавиатуры?

Я знаю, как прервать ядро ​​(например, нажав I дважды или прерывая ядро ​​в веб-интерфейсе). Тем не менее, я построил C-расширение для Python (я использую Windows), которое обрабатывает события CTRL-C в моем коде C++ (игрушечный пример):

static int s_interrupted = 0;

BOOL WINAPI consoleHandler(DWORD fdwCtrlType) {

  switch (fdwCtrlType)
  {
  // Handle the CTRL-C signal.
  case CTRL_C_EVENT:
      s_interrupted = 1;
      return TRUE;
  }
}

int main() {
    s_interrupted = 0;
    int output = 1;
    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) {
        std::cout<<"ERROR: Could not set control handler"<<std::endl;
    } else {
        std::cout<<"Control hanlder installed"<<std::endl;
    }
    int k = 10000;
    while (int i < k) {
        if (s_interrupted == 1) {
            output = -1;
            break;
        }
         output = i
         i = i + 1;
    }
    return output;
}

Вывод моей основной программы меняется в зависимости от значения s_interrupted, Другими словами, если я не нажму CTRL+C, программа завершит цикл while и вернет целое число. Если я нажму CTRL+C, цикл while будет прерван и вернет другое целое число. Я не ожидаю увидеть KeyboardInterrupt в моем терминале Python.

Он прекрасно работает, когда я вызываю это расширение C в терминале. Однако, когда я делаю это в блокноте Jupyter, программа ведет себя так, как будто я никогда не прерывал ядро. Юпитер не посылает SIGINT когда я прерываю ядро?

2 ответа

Вы не можете использовать consoleHandler() здесь, потому что нет консоли. Ядро IPython является "безголовым" дочерним процессом, который выполняет код по запросу, направляемый внешним интерфейсом Jupyter.

Чтобы прервать работающее ядро ​​IPython, интерфейс Jupyter использует SIGINT сигнал. Это делается как на POSIX, так и на Windows; в Windows Jupyter использует дополнительную инфраструктуру, построенную вокруг CreateEventA, SetEvent а также WaitForMultipleObjects добиться того же результата, что и POSIX os.killpg(PID, SIGINT) вызов; отдельный поток опрашивает событие и запускает SIGINT сигнал в основной теме.

Обратите внимание, что ядро ​​IPython явно восстанавливает обработчик сигналов Python по умолчанию для каждого сообщения, которое оно обрабатывает. Увидеть ipykernel.kernelbase.Kernel реализации для pre_ а также post_handler_hook методы:

 self.saved_sigint_handler = signal(SIGINT, default_int_handler)

а также

 signal(SIGINT, self.saved_sigint_handler)

Эти две перехваты выполняются до и после каждого обработчика сообщений (то есть для каждого сообщения, отправляемого веб-интерфейсом процессу ядра, включая сообщения execute).

это signal.default_int_handler что поднимает KeyboardInterrupt в основном потоке Python. Если ваш код должен обнаруживать прерывания, он должен будет зарегистрировать свой собственный signal обработчик каждый раз, когда IPython запускает ячейку.

Примечание: автономный интерактивный интерпретатор Python не использует SetConsoleCtrlHandler также обнаруживать прерывания клавиатуры; единственное место, которое используется в исходном коде Python, находится в py лаунчер, и только потом заглушить управляющие коды с помощью обработчика, который возвращает TRUE всегда. Вместо этого Python полагается на Windows, отправляющую SIGINT сигнал всем подключенным процессам консоли для активного окна консоли.

Ваш код не устанавливает обработчики сигналов и s_signal_handler не используется Вам нужно позвонить signal функция для регистрации вашего обратного вызова.

#include <atomic>
#include <signal.h>

::std::atomic<bool> s_interrupted{};

static void signal_handler(int signal)
{
  s_interrupted = true;
}

int main()
{
    ::signal(SIGINT, &::signal_handler);
Другие вопросы по тегам