diff -r f6b8b1e5d24d Doc/c-api/exceptions.rst --- a/Doc/c-api/exceptions.rst Wed Jun 22 22:46:51 2016 -0400 +++ b/Doc/c-api/exceptions.rst Mon Jun 27 11:06:43 2016 +0200 @@ -492,18 +492,26 @@ Signal Handling cleared if it was previously set. -.. c:function:: void PyErr_SetInterrupt() +.. c:function:: int PyErr_SetInterruptWithErr() .. index:: single: SIGINT - single: KeyboardInterrupt (built-in exception) - This function simulates the effect of a :const:`SIGINT` signal arriving --- the - next time :c:func:`PyErr_CheckSignals` is called, :exc:`KeyboardInterrupt` will - be raised. It may be called without holding the interpreter lock. + Simulate the effect of a :data:`signal.SIGINT` signal arriving. The next + time :c:func:`PyErr_CheckSignals` is called, the Python + :data:`signal.SIGINT` signal handler will be raised. - .. % XXX This was described as obsolete, but is used in - .. % _thread.interrupt_main() (used from IDLE), so it's still needed. + The :data:`signal.SIGINT` signal must be handled by Python, otherwise an + exception is raised and return ``-1`` on error. Return ``0`` on success. + + The GIL doesn't need to be hold to call this function. + + .. versionadded:: 3.6 + + +.. c:function:: void PyErr_SetInterrupt() + + Deprecated version :c:func:`PyErr_SetInterruptWithErr` which ignores errors. .. c:function:: int PySignal_SetWakeupFd(int fd) diff -r f6b8b1e5d24d Doc/library/_thread.rst --- a/Doc/library/_thread.rst Wed Jun 22 22:46:51 2016 -0400 +++ b/Doc/library/_thread.rst Mon Jun 27 11:06:43 2016 +0200 @@ -57,8 +57,15 @@ It defines the following constants and f .. function:: interrupt_main() - Raise a :exc:`KeyboardInterrupt` exception in the main thread. A subthread can - use this function to interrupt the main thread. + Simulate the effect of a :data:`signal.SIGINT` signal arriving in the main + thread. A thread can use this function to interrupt the main thread. + + The :data:`signal.SIGINT` signal must be handled by Python, otherwise + an exception is raised. + + .. versionchanged:: 3.6 + The function now raises an exception if the signal is ignored or not + handled by Python. .. function:: exit() diff -r f6b8b1e5d24d Lib/test/test_threading.py --- a/Lib/test/test_threading.py Wed Jun 22 22:46:51 2016 -0400 +++ b/Lib/test/test_threading.py Mon Jun 27 11:06:43 2016 +0200 @@ -10,6 +10,7 @@ import random import sys _thread = import_module('_thread') threading = import_module('threading') +import signal import time import unittest import weakref @@ -1097,6 +1098,7 @@ class BoundedSemaphoreTests(lock_tests.B class BarrierTests(lock_tests.BarrierTests): barriertype = staticmethod(threading.Barrier) + class MiscTestCase(unittest.TestCase): def test__all__(self): extra = {"ThreadError"} @@ -1104,5 +1106,43 @@ class MiscTestCase(unittest.TestCase): support.check__all__(self, threading, ('threading', '_thread'), extra=extra, blacklist=blacklist) + +class InterruptMainTests(unittest.TestCase): + def test_interrupt_main_subthread(self): + #Calling start_new_thread with a function that executes interrupt_main + # should raise KeyboardInterrupt upon completion. + def call_interrupt(): + _thread.interrupt_main() + t = threading.Thread(target=call_interrupt) + with self.assertRaises(KeyboardInterrupt): + t.start() + t.join() + t.join() + + def test_interrupt_main_mainthread(self): + # Make sure that if interrupt_main is called in main threat that + # KeyboardInterrupt is raised instantly. + with self.assertRaises(KeyboardInterrupt): + _thread.interrupt_main() + + def test_interrupt_main_error(self): + handler = signal.getsignal(signal.SIGINT) + try: + signal.signal(signal.SIGINT, signal.SIG_IGN) + with self.assertRaises(RuntimeError) as cm: + _thread.interrupt_main() + self.assertEqual(str(cm.exception), + 'the SIGINT signal is ignored') + + signal.signal(signal.SIGINT, signal.SIG_DFL) + with self.assertRaises(RuntimeError) as cm: + _thread.interrupt_main() + self.assertEqual(str(cm.exception), + 'the SIGINT signal is not handled by Python') + + finally: + signal.signal(signal.SIGINT, handler) + + if __name__ == "__main__": unittest.main() diff -r f6b8b1e5d24d Misc/NEWS --- a/Misc/NEWS Wed Jun 22 22:46:51 2016 -0400 +++ b/Misc/NEWS Mon Jun 27 11:06:43 2016 +0200 @@ -10,6 +10,9 @@ What's New in Python 3.6.0 alpha 3 Library ------- +- Issue #23395: _thread.interrupt_main() now raises an exception if the SIGINT + signal is ignored or not handled by Python. + - Issue #18726: All optional parameters of the dump(), dumps(), load() and loads() functions and JSONEncoder and JSONDecoder class constructors in the json module are now keyword-only. diff -r f6b8b1e5d24d Modules/_threadmodule.c --- a/Modules/_threadmodule.c Wed Jun 22 22:46:51 2016 -0400 +++ b/Modules/_threadmodule.c Mon Jun 27 11:06:43 2016 +0200 @@ -1110,9 +1110,10 @@ thread to exit silently unless the excep static PyObject * thread_PyThread_interrupt_main(PyObject * self) { - PyErr_SetInterrupt(); - Py_INCREF(Py_None); - return Py_None; + if (PyErr_SetInterruptWithErr() < 0) { + return NULL; + } + Py_RETURN_NONE; } PyDoc_STRVAR(interrupt_doc, diff -r f6b8b1e5d24d Modules/signalmodule.c --- a/Modules/signalmodule.c Wed Jun 22 22:46:51 2016 -0400 +++ b/Modules/signalmodule.c Mon Jun 27 11:06:43 2016 +0200 @@ -1591,9 +1591,39 @@ PyErr_CheckSignals(void) } -/* Replacements for intrcheck.c functionality - * Declared in pyerrors.h - */ +/* Simulate the effect of a SIGINT signal arriving. The next time + PyErr_CheckSignals() is called, the Python SIGINT signal handler will be + raised. + + The SIGINT signal must be handled by Python, otherwise an exception is + raised and return -1. Return 0 on success. + + The GIL doesn't need to be hold to call this function. */ +int +PyErr_SetInterruptWithErr(void) +{ + if (Handlers[SIGINT].func == IgnoreHandler) { + PyErr_SetString(PyExc_RuntimeError, + "the SIGINT signal is ignored"); + return -1; + } + + if (Handlers[SIGINT].func == DefaultHandler) { + PyErr_SetString(PyExc_RuntimeError, + "the SIGINT signal is not handled by Python"); + return -1; + } + + trip_signal(SIGINT); + return 0; +} + + +/* Simulate the effect of a SIGINT signal arriving. The next time + PyErr_CheckSignals() is called, the Python SIGINT signal handler will be + raised. + + The GIL doesn't need to be hold to call this function. */ void PyErr_SetInterrupt(void) {