Windows: Как Jupyter генерирует прерывание клавиатуры? - PullRequest
0 голосов
/ 15 января 2019

Я знаю, как прерывать ядро ​​(например, дважды нажав 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, программа ведет себя так, как будто я никогда не прерывал ядро. Разве Jupyter не посылает SIGINT, когда я прерываю ядро?

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Вы не можете использовать 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)

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

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

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

0 голосов
/ 15 января 2019

Ваш код не устанавливает обработчики сигналов и 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);
...