Skip to content

Commit 310f414

Browse files
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. (cherry picked from commit 608876b) Co-authored-by: Matěj Cepl <[email protected]>
1 parent a3488e5 commit 310f414

File tree

6 files changed

+59
-13
lines changed

6 files changed

+59
-13
lines changed

Doc/c-api/exceptions.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,13 @@ Signal Handling
516516
single: SIGINT
517517
single: KeyboardInterrupt (built-in exception)
518518
519-
This function simulates the effect of a :const:`SIGINT` signal arriving --- the
520-
next time :c:func:`PyErr_CheckSignals` is called, :exc:`KeyboardInterrupt` will
521-
be raised. It may be called without holding the interpreter lock.
522-
523-
.. % XXX This was described as obsolete, but is used in
524-
.. % _thread.interrupt_main() (used from IDLE), so it's still needed.
519+
Simulate the effect of a :const:`SIGINT` signal arriving. The next time
520+
:c:func:`PyErr_CheckSignals` is called, the Python signal handler for
521+
:const:`SIGINT` will be called.
525522
523+
If :const:`SIGINT` isn't handled by Python (it was set to
524+
:data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
525+
nothing.
526526
527527
.. c:function:: int PySignal_SetWakeupFd(int fd)
528528

Doc/library/_thread.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ This module defines the following constants and functions:
5353

5454
.. function:: interrupt_main()
5555

56-
Raise a :exc:`KeyboardInterrupt` exception in the main thread. A subthread can
57-
use this function to interrupt the main thread.
56+
Simulate the effect of a :data:`signal.SIGINT` signal arriving in the main
57+
thread. A thread can use this function to interrupt the main thread.
58+
59+
If :data:`signal.SIGINT` isn't handled by Python (it was set to
60+
:data:`signal.SIG_DFL` or :data:`signal.SIG_IGN`), this function does
61+
nothing.
5862

5963

6064
.. function:: exit()

Lib/test/test_threading.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import weakref
1717
import os
1818
import subprocess
19+
import signal
1920

2021
from test import lock_tests
2122
from test import support
@@ -1165,12 +1166,46 @@ class BoundedSemaphoreTests(lock_tests.BoundedSemaphoreTests):
11651166
class BarrierTests(lock_tests.BarrierTests):
11661167
barriertype = staticmethod(threading.Barrier)
11671168

1169+
11681170
class MiscTestCase(unittest.TestCase):
11691171
def test__all__(self):
11701172
extra = {"ThreadError"}
11711173
blacklist = {'currentThread', 'activeCount'}
11721174
support.check__all__(self, threading, ('threading', '_thread'),
11731175
extra=extra, blacklist=blacklist)
11741176

1177+
1178+
class InterruptMainTests(unittest.TestCase):
1179+
def test_interrupt_main_subthread(self):
1180+
# Calling start_new_thread with a function that executes interrupt_main
1181+
# should raise KeyboardInterrupt upon completion.
1182+
def call_interrupt():
1183+
_thread.interrupt_main()
1184+
t = threading.Thread(target=call_interrupt)
1185+
with self.assertRaises(KeyboardInterrupt):
1186+
t.start()
1187+
t.join()
1188+
t.join()
1189+
1190+
def test_interrupt_main_mainthread(self):
1191+
# Make sure that if interrupt_main is called in main thread that
1192+
# KeyboardInterrupt is raised instantly.
1193+
with self.assertRaises(KeyboardInterrupt):
1194+
_thread.interrupt_main()
1195+
1196+
def test_interrupt_main_noerror(self):
1197+
handler = signal.getsignal(signal.SIGINT)
1198+
try:
1199+
# No exception should arise.
1200+
signal.signal(signal.SIGINT, signal.SIG_IGN)
1201+
_thread.interrupt_main()
1202+
1203+
signal.signal(signal.SIGINT, signal.SIG_DFL)
1204+
_thread.interrupt_main()
1205+
finally:
1206+
# Restore original handler
1207+
signal.signal(signal.SIGINT, handler)
1208+
1209+
11751210
if __name__ == "__main__":
11761211
unittest.main()

Misc/ACKS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ Donn Cave
257257
Charles Cazabon
258258
Jesús Cea Avión
259259
Per Cederqvist
260-
Matej Cepl
260+
Matěj Cepl
261261
Carl Cerecke
262262
Octavian Cerna
263263
Michael Cetrulo
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``_thread.interrupt_main()`` now avoids setting the Python error status
2+
if the ``SIGINT`` signal is ignored or not handled by Python.

Modules/signalmodule.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,13 +1562,18 @@ PyErr_CheckSignals(void)
15621562
}
15631563

15641564

1565-
/* Replacements for intrcheck.c functionality
1566-
* Declared in pyerrors.h
1567-
*/
1565+
/* Simulate the effect of a signal.SIGINT signal arriving. The next time
1566+
PyErr_CheckSignals is called, the Python SIGINT signal handler will be
1567+
raised.
1568+
1569+
Missing signal handler for the SIGINT signal is silently ignored. */
15681570
void
15691571
PyErr_SetInterrupt(void)
15701572
{
1571-
trip_signal(SIGINT);
1573+
if ((Handlers[SIGINT].func != IgnoreHandler) &&
1574+
(Handlers[SIGINT].func != DefaultHandler)) {
1575+
trip_signal(SIGINT);
1576+
}
15721577
}
15731578

15741579
void

0 commit comments

Comments
 (0)