Skip to content

Commit

Permalink
bpo-23395: Fix PyErr_SetInterrupt if the SIGINT signal is ignored or …
Browse files Browse the repository at this point in the history
…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.
(cherry picked from commit 608876b)

Co-authored-by: Matěj Cepl <mcepl@cepl.eu>
  • Loading branch information
miss-islington and mcepl committed May 24, 2019
1 parent a3488e5 commit 310f414
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 13 deletions.
12 changes: 6 additions & 6 deletions Doc/c-api/exceptions.rst
Expand Up @@ -516,13 +516,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)
Expand Down
8 changes: 6 additions & 2 deletions Doc/library/_thread.rst
Expand Up @@ -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()
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_threading.py
Expand Up @@ -16,6 +16,7 @@
import weakref
import os
import subprocess
import signal

from test import lock_tests
from test import support
Expand Down Expand Up @@ -1165,12 +1166,46 @@ class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests):
class BarrierTests(lock_tests.BarrierTests):
barriertype = staticmethod(threading.Barrier)


class MiscTestCase(unittest.TestCase):
def test__all__(self):
extra = {"ThreadError"}
blacklist = {'currentThread', 'activeCount'}
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()
2 changes: 1 addition & 1 deletion Misc/ACKS
Expand Up @@ -257,7 +257,7 @@ Donn Cave
Charles Cazabon
Jesús Cea Avión
Per Cederqvist
Matej Cepl
Matěj Cepl
Carl Cerecke
Octavian Cerna
Michael Cetrulo
Expand Down
@@ -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.
13 changes: 9 additions & 4 deletions Modules/signalmodule.c
Expand Up @@ -1562,13 +1562,18 @@ 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)
{
trip_signal(SIGINT);
if ((Handlers[SIGINT].func != IgnoreHandler) &&
(Handlers[SIGINT].func != DefaultHandler)) {
trip_signal(SIGINT);
}
}

void
Expand Down

0 comments on commit 310f414

Please sign in to comment.