Skip to content

Commit 9743c55

Browse files
vstinnerlisroach
authored andcommitted
bpo-37266: Daemon threads are now denied in subinterpreters (pythonGH-14049)
In a subinterpreter, spawning a daemon thread now raises an exception. Daemon threads were never supported in subinterpreters. Previously, the subinterpreter finalization crashed with a Pyton fatal error if a daemon thread was still running. * Add _thread._is_main_interpreter() * threading.Thread.start() now raises RuntimeError if the thread is a daemon thread and the method is called from a subinterpreter. * The _thread module now uses Argument Clinic for the new function. * Use textwrap.dedent() in test_threading.SubinterpThreadingTests
1 parent a80864d commit 9743c55

File tree

8 files changed

+113
-29
lines changed

8 files changed

+113
-29
lines changed

Doc/library/threading.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ since it is impossible to detect the termination of alien threads.
280280
base class constructor (``Thread.__init__()``) before doing anything else to
281281
the thread.
282282

283+
Daemon threads must not be used in subinterpreters.
284+
283285
.. versionchanged:: 3.3
284286
Added the *daemon* argument.
285287

@@ -294,6 +296,12 @@ since it is impossible to detect the termination of alien threads.
294296
This method will raise a :exc:`RuntimeError` if called more than once
295297
on the same thread object.
296298

299+
Raise a :exc:`RuntimeError` if the thread is a daemon thread and the
300+
method is called from a subinterpreter.
301+
302+
.. versionchanged:: 3.9
303+
In a subinterpreter, spawning a daemon thread now raises an exception.
304+
297305
.. method:: run()
298306

299307
Method representing the thread's activity.

Doc/whatsnew/3.9.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ New Modules
8686
Improved Modules
8787
================
8888

89+
threading
90+
---------
91+
92+
In a subinterpreter, spawning a daemon thread now raises an exception. Daemon
93+
threads were never supported in subinterpreters. Previously, the subinterpreter
94+
finalization crashed with a Pyton fatal error if a daemon thread was still
95+
running.
96+
8997

9098
Optimizations
9199
=============

Lib/_dummy_thread.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,7 @@ def interrupt_main():
161161
else:
162162
global _interrupt
163163
_interrupt = True
164+
165+
166+
def _is_main_interpreter():
167+
return True

Lib/test/test_threading.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import os
1818
import subprocess
1919
import signal
20+
import textwrap
2021

2122
from test import lock_tests
2223
from test import support
@@ -928,14 +929,19 @@ def test_clear_threads_states_after_fork(self):
928929

929930

930931
class SubinterpThreadingTests(BaseTestCase):
932+
def pipe(self):
933+
r, w = os.pipe()
934+
self.addCleanup(os.close, r)
935+
self.addCleanup(os.close, w)
936+
if hasattr(os, 'set_blocking'):
937+
os.set_blocking(r, False)
938+
return (r, w)
931939

932940
def test_threads_join(self):
933941
# Non-daemon threads should be joined at subinterpreter shutdown
934942
# (issue #18808)
935-
r, w = os.pipe()
936-
self.addCleanup(os.close, r)
937-
self.addCleanup(os.close, w)
938-
code = r"""if 1:
943+
r, w = self.pipe()
944+
code = textwrap.dedent(r"""
939945
import os
940946
import random
941947
import threading
@@ -953,7 +959,7 @@ def f():
953959
954960
threading.Thread(target=f).start()
955961
random_sleep()
956-
""" % (w,)
962+
""" % (w,))
957963
ret = test.support.run_in_subinterp(code)
958964
self.assertEqual(ret, 0)
959965
# The thread was joined properly.
@@ -964,10 +970,8 @@ def test_threads_join_2(self):
964970
# Python code returned but before the thread state is deleted.
965971
# To achieve this, we register a thread-local object which sleeps
966972
# a bit when deallocated.
967-
r, w = os.pipe()
968-
self.addCleanup(os.close, r)
969-
self.addCleanup(os.close, w)
970-
code = r"""if 1:
973+
r, w = self.pipe()
974+
code = textwrap.dedent(r"""
971975
import os
972976
import random
973977
import threading
@@ -992,34 +996,38 @@ def f():
992996
993997
threading.Thread(target=f).start()
994998
random_sleep()
995-
""" % (w,)
999+
""" % (w,))
9961000
ret = test.support.run_in_subinterp(code)
9971001
self.assertEqual(ret, 0)
9981002
# The thread was joined properly.
9991003
self.assertEqual(os.read(r, 1), b"x")
10001004

1001-
@cpython_only
1002-
def test_daemon_threads_fatal_error(self):
1003-
subinterp_code = r"""if 1:
1004-
import os
1005+
def test_daemon_thread(self):
1006+
r, w = self.pipe()
1007+
code = textwrap.dedent(f"""
10051008
import threading
1006-
import time
1009+
import sys
10071010
1008-
def f():
1009-
# Make sure the daemon thread is still running when
1010-
# Py_EndInterpreter is called.
1011-
time.sleep(10)
1012-
threading.Thread(target=f, daemon=True).start()
1013-
"""
1014-
script = r"""if 1:
1015-
import _testcapi
1011+
channel = open({w}, "w", closefd=False)
1012+
1013+
def func():
1014+
pass
1015+
1016+
thread = threading.Thread(target=func, daemon=True)
1017+
try:
1018+
thread.start()
1019+
except RuntimeError as exc:
1020+
print("ok: %s" % exc, file=channel, flush=True)
1021+
else:
1022+
thread.join()
1023+
print("fail: RuntimeError not raised", file=channel, flush=True)
1024+
""")
1025+
ret = test.support.run_in_subinterp(code)
1026+
self.assertEqual(ret, 0)
10161027

1017-
_testcapi.run_in_subinterp(%r)
1018-
""" % (subinterp_code,)
1019-
with test.support.SuppressCrashReport():
1020-
rc, out, err = assert_python_failure("-c", script)
1021-
self.assertIn("Fatal Python error: Py_EndInterpreter: "
1022-
"not the last thread", err.decode())
1028+
msg = os.read(r, 100).decode().rstrip()
1029+
self.assertEqual("ok: daemon thread are not supported "
1030+
"in subinterpreters", msg)
10231031

10241032

10251033
class ThreadingExceptionTests(BaseTestCase):

Lib/threading.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
_allocate_lock = _thread.allocate_lock
3535
_set_sentinel = _thread._set_sentinel
3636
get_ident = _thread.get_ident
37+
_is_main_interpreter = _thread._is_main_interpreter
3738
try:
3839
get_native_id = _thread.get_native_id
3940
_HAVE_THREAD_NATIVE_ID = True
@@ -846,6 +847,11 @@ def start(self):
846847

847848
if self._started.is_set():
848849
raise RuntimeError("threads can only be started once")
850+
851+
if self.daemon and not _is_main_interpreter():
852+
raise RuntimeError("daemon thread are not supported "
853+
"in subinterpreters")
854+
849855
with _active_limbo_lock:
850856
_limbo[self] = self
851857
try:
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
In a subinterpreter, spawning a daemon thread now raises an exception. Daemon
2+
threads were never supported in subinterpreters. Previously, the subinterpreter
3+
finalization crashed with a Pyton fatal error if a daemon thread was still
4+
running.

Modules/_threadmodule.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88
#include "structmember.h" /* offsetof */
99
#include "pythread.h"
1010

11+
#include "clinic/_threadmodule.c.h"
12+
13+
/*[clinic input]
14+
module _thread
15+
[clinic start generated code]*/
16+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=be8dbe5cc4b16df7]*/
17+
18+
1119
static PyObject *ThreadError;
1220
static PyObject *str_dict;
1321

@@ -1442,6 +1450,21 @@ PyDoc_STRVAR(excepthook_doc,
14421450
\n\
14431451
Handle uncaught Thread.run() exception.");
14441452

1453+
/*[clinic input]
1454+
_thread._is_main_interpreter
1455+
1456+
Return True if the current interpreter is the main Python interpreter.
1457+
[clinic start generated code]*/
1458+
1459+
static PyObject *
1460+
_thread__is_main_interpreter_impl(PyObject *module)
1461+
/*[clinic end generated code: output=7dd82e1728339adc input=cc1eb00fd4598915]*/
1462+
{
1463+
_PyRuntimeState *runtime = &_PyRuntime;
1464+
PyInterpreterState *interp = _PyRuntimeState_GetThreadState(runtime)->interp;
1465+
return PyBool_FromLong(interp == runtime->interpreters.main);
1466+
}
1467+
14451468
static PyMethodDef thread_methods[] = {
14461469
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
14471470
METH_VARARGS, start_new_doc},
@@ -1471,6 +1494,7 @@ static PyMethodDef thread_methods[] = {
14711494
METH_NOARGS, _set_sentinel_doc},
14721495
{"_excepthook", thread_excepthook,
14731496
METH_O, excepthook_doc},
1497+
_THREAD__IS_MAIN_INTERPRETER_METHODDEF
14741498
{NULL, NULL} /* sentinel */
14751499
};
14761500

Modules/clinic/_threadmodule.c.h

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)