bpo-23395: Fix PyErr_SetInterrupt if the SIGINT signal is ignored or not handled (GH-7778)

``_thread.interrupt_main()`` now avoids setting the Python error status if the ``SIGINT`` signal is ignored or not handled by Python.
This commit is contained in:
Matěj Cepl 2019-05-23 22:30:00 +02:00 committed by Antoine Pitrou
parent b82e17e626
commit 608876b6b1
6 changed files with 59 additions and 13 deletions

View File

@ -519,13 +519,13 @@ Signal Handling
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.
.. % XXX This was described as obsolete, but is used in
.. % _thread.interrupt_main() (used from IDLE), so it's still needed.
Simulate the effect of a :const:`SIGINT` signal arriving. The next time
:c:func:`PyErr_CheckSignals` is called, the Python signal handler for
:const:`SIGINT` will be called.
If :const:`SIGINT` isn't handled by Python (it was set to
:data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
nothing.
.. c:function:: int PySignal_SetWakeupFd(int fd)

View File

@ -53,8 +53,12 @@ This module defines the following constants and functions:
.. 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.
If :data:`signal.SIGINT` isn't handled by Python (it was set to
:data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
nothing.
.. function:: exit()

View File

@ -16,6 +16,7 @@ import unittest
import weakref
import os
import subprocess
import signal
from test import lock_tests
from test import support
@ -1168,6 +1169,7 @@ class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests):
class BarrierTests(lock_tests.BarrierTests):
barriertype = staticmethod(threading.Barrier)
class MiscTestCase(unittest.TestCase):
def test__all__(self):
extra = {"ThreadError"}
@ -1175,5 +1177,38 @@ 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 thread that
# KeyboardInterrupt is raised instantly.
with self.assertRaises(KeyboardInterrupt):
_thread.interrupt_main()
def test_interrupt_main_noerror(self):
handler = signal.getsignal(signal.SIGINT)
try:
# No exception should arise.
signal.signal(signal.SIGINT, signal.SIG_IGN)
_thread.interrupt_main()
signal.signal(signal.SIGINT, signal.SIG_DFL)
_thread.interrupt_main()
finally:
# Restore original handler
signal.signal(signal.SIGINT, handler)
if __name__ == "__main__":
unittest.main()

View File

@ -261,7 +261,7 @@ Donn Cave
Charles Cazabon
Jesús Cea Avión
Per Cederqvist
Matej Cepl
Matěj Cepl
Carl Cerecke
Octavian Cerna
Michael Cetrulo

View File

@ -0,0 +1,2 @@
``_thread.interrupt_main()`` now avoids setting the Python error status
if the ``SIGINT`` signal is ignored or not handled by Python.

View File

@ -1683,14 +1683,19 @@ _PyErr_CheckSignals(void)
}
/* Replacements for intrcheck.c functionality
* Declared in pyerrors.h
*/
/* Simulate the effect of a signal.SIGINT signal arriving. The next time
PyErr_CheckSignals is called, the Python SIGINT signal handler will be
raised.
Missing signal handler for the SIGINT signal is silently ignored. */
void
PyErr_SetInterrupt(void)
{
if ((Handlers[SIGINT].func != IgnoreHandler) &&
(Handlers[SIGINT].func != DefaultHandler)) {
trip_signal(SIGINT);
}
}
void
PyOS_InitInterrupts(void)