-1

I know how to interrupt the kernel (such as by tapping I twice or by interrupting the kernel on the web interface). However, I built a C-extension for Python (I'm using Windows) that handles CTRL-C events in my C++ code (a toy example):

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;
}

The output of my main program changes depending on the value of s_interrupted. In other words, if I don't press CTRL+C, the program will finish the while loop and return an integer. If I press CTRL+C, the while loop will be terminated and return a different integer. I don't expect to see a KeyboardInterrupt in my Python terminal.

It works fine, when I call this C extension in the terminal. However, when I do it in a Jupyter notebook, the program behaves as if I never interrupted the kernel. Does Jupyter not send a SIGINT when I interrupt the kernel?

user1691278
  • 1,751
  • 1
  • 15
  • 20
  • Have you read the [Jupyter messaging documentation](https://jupyter-client.readthedocs.io/en/stable/messaging.html)? Restarting a kernel is just another message from front-end to back-end. – Martijn Pieters Jan 16 '19 at 11:40

2 Answers2

1

Your code does not install signal handlers and s_signal_handler is not used. You need to call signal function to register your callback.

#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);
user7860670
  • 35,849
  • 4
  • 58
  • 84
1

You can't use consoleHandler() here, because there is no console. The IPython kernel is a 'headless' child process that executes code on request, directed by the Jupyter frontend.

To interrupt a running IPython kernel, the Jupyter front-end uses the SIGINT signal. It does so on both POSIX and on Windows; on Windows Jupyter uses additional infrastructure built around CreateEventA, SetEvent and WaitForMultipleObjects to achieve the same result as a POSIX os.killpg(PID, SIGINT) call; a separate thread polls for the event and triggers a SIGINT signal in the main thread.

Note that the IPython kernel explicitly restores the Python default signal handler for every message it handles. See the ipykernel.kernelbase.Kernel implementations for the pre_ and post_handler_hook methods:

 self.saved_sigint_handler = signal(SIGINT, default_int_handler)

and

 signal(SIGINT, self.saved_sigint_handler)

These two hooks are executed before and after every message handler (so for every message sent by the frontend to the kernel process, including execute messages).

It is signal.default_int_handler that raises KeyboardInterrupt in the main Python thread. If your code must detect interrupts, it will need to register its own signal handler each time IPython executes a cell.

As a side note: a standalone interactive Python interpreter doesn't use SetConsoleCtrlHandler to detect keyboard interrupts either; the only place that is used in the Python source code is in the py launcher, and only then to silence control codes with a handler that returns TRUE, always. Instead, Python relies on Windows sending the SIGINT signal to all attached console processes for the active console window.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343