Allowing Ctrl-C to interrupt a python C-extension

隐身守侯 提交于 2019-12-18 04:07:46

问题


I'm running some computationally heavy simulation in (home-made) C-based python extensions. Occasionally I get stuff wrong and would like to terminate a simulation. However, Ctrl-C doesn't seem to have any effect (other than printing ^C to the screen) so I have to kill the process using kill or the system monitor.

As far as I can see python just waits for the C extension to finish and doesn't really communicate with it during this time.

Is there a way to make this work?

Update: The main answers (for my specific problem) turned out to be: 1. rewrite the code to regularly pass control back to the caller (answer Allowing Ctrl-C to interrupt a python C-extension below), or 2. Use PyErr_CheckSignals() (answer https://stackoverflow.com/a/33652496/423420 below)


回答1:


I would redesign the C extensions so that they don't run for a long period.

So, split them into more elementary steps (each running for a short period of time, e.g. 10 to 50 milliseconds), and have these more elementary steps called by Python code.

continuation passing style might be relevant to understand, as a programming style...




回答2:


However, Ctrl-C doesn't seem to have any effect

Ctrl-C in the shell sends SIGINT to the foreground process group. python on receiving the signal sets a flag in C code. If your C extension runs in the main thread then no Python signal handler will be run (and therefore you won't see KeyboardInterrupt exception on Ctrl-C) unless you call PyErr_CheckSignals() that checks the flag (it means: it shouldn't slow you down) and runs Python signal handlers if necessary or if your simulation allows Python code to execute (e.g., if the simulation uses Python callbacks). If the extension runs in a background thread then it is enough to release GIL (to allow Python code to run in the main thread that enables the signal handlers to run).

Related: Cython, Python and KeybordInterrupt ingored




回答3:


Python has a signal handler installed on SIGINT which simply sets a flag that is checked by the main interpreter loop. For this handler to work properly, the Python interpreter has to be running Python code.

You have a couple of options available to you:

  1. Use Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS to release the GIL around your C extension code. You cannot use any Python functions when not holding the GIL, but Python code (and other C code) may run concurrently with your C thread (true multithreading). A separate Python thread can execute alongside the C extension and catch Ctrl+C signals.
  2. Set up your own SIGINT handler and call the original (Python) signal handler. Your SIGINT handler can then do whatever it needs to do to cancel the C extension code and return control to the Python interpreter.



回答4:


There is an alternative way to solve this problem if you do not want your C Extension (or ctypes DLL) to be tied to Python, such as a case where you want to create a C library with bindings in multiple languages, you must allow your C Extension to run for long periods, and you can modify the C Extension:

Include the signal header in the C Extension.

#include <signal.h>

Create a signal handler typedef in the C Extension.

typedef void (*sighandler_t)(int);

Add signal handlers in the C extension that will perform the actions necessary to interrupt any long running code (set a stop flag, etc.), and save the existing Python signal handlers.

sighandler_t old_sig_int_handler = signal(SIGINT, your_sig_handler);
sighandler_t old_sig_term_handler = signal(SIGTERM, your_sig_handler);

Restore the existing signal handlers whenever the C extension returns. This step ensures that the Python signal handlers are re-applied.

signal(SIGINT, old_sig_int_handler);
signal(SIGTERM, old_sig_term_handler);

If the long-running code is interrupted (flag, etc.), return control to Python with a return code indicating the signal number.

return SIGINT;

In Python, send the signal received in the C extension.

import os
import signal

status = c_extension.run()

if status in [signal.SIGINT, signal.SIGTERM]:
    os.kill(os.getpid(), status)

Python will perform the action you are expecting, such as raising a KeyboardInterrupt for SIGINT.



来源:https://stackoverflow.com/questions/14707049/allowing-ctrl-c-to-interrupt-a-python-c-extension

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!