mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-37266: Daemon threads are now denied in subinterpreters (GH-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
This commit is contained in:
parent
212646cae6
commit
066e5b1a91
8 changed files with 113 additions and 29 deletions
|
@ -280,6 +280,8 @@ since it is impossible to detect the termination of alien threads.
|
||||||
base class constructor (``Thread.__init__()``) before doing anything else to
|
base class constructor (``Thread.__init__()``) before doing anything else to
|
||||||
the thread.
|
the thread.
|
||||||
|
|
||||||
|
Daemon threads must not be used in subinterpreters.
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
Added the *daemon* argument.
|
Added the *daemon* argument.
|
||||||
|
|
||||||
|
@ -294,6 +296,12 @@ since it is impossible to detect the termination of alien threads.
|
||||||
This method will raise a :exc:`RuntimeError` if called more than once
|
This method will raise a :exc:`RuntimeError` if called more than once
|
||||||
on the same thread object.
|
on the same thread object.
|
||||||
|
|
||||||
|
Raise a :exc:`RuntimeError` if the thread is a daemon thread and the
|
||||||
|
method is called from a subinterpreter.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
In a subinterpreter, spawning a daemon thread now raises an exception.
|
||||||
|
|
||||||
.. method:: run()
|
.. method:: run()
|
||||||
|
|
||||||
Method representing the thread's activity.
|
Method representing the thread's activity.
|
||||||
|
|
|
@ -86,6 +86,14 @@ New Modules
|
||||||
Improved Modules
|
Improved Modules
|
||||||
================
|
================
|
||||||
|
|
||||||
|
threading
|
||||||
|
---------
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -161,3 +161,7 @@ def interrupt_main():
|
||||||
else:
|
else:
|
||||||
global _interrupt
|
global _interrupt
|
||||||
_interrupt = True
|
_interrupt = True
|
||||||
|
|
||||||
|
|
||||||
|
def _is_main_interpreter():
|
||||||
|
return True
|
||||||
|
|
|
@ -17,6 +17,7 @@ import weakref
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import signal
|
import signal
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from test import lock_tests
|
from test import lock_tests
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -928,14 +929,19 @@ class ThreadJoinOnShutdown(BaseTestCase):
|
||||||
|
|
||||||
|
|
||||||
class SubinterpThreadingTests(BaseTestCase):
|
class SubinterpThreadingTests(BaseTestCase):
|
||||||
|
def pipe(self):
|
||||||
|
r, w = os.pipe()
|
||||||
|
self.addCleanup(os.close, r)
|
||||||
|
self.addCleanup(os.close, w)
|
||||||
|
if hasattr(os, 'set_blocking'):
|
||||||
|
os.set_blocking(r, False)
|
||||||
|
return (r, w)
|
||||||
|
|
||||||
def test_threads_join(self):
|
def test_threads_join(self):
|
||||||
# Non-daemon threads should be joined at subinterpreter shutdown
|
# Non-daemon threads should be joined at subinterpreter shutdown
|
||||||
# (issue #18808)
|
# (issue #18808)
|
||||||
r, w = os.pipe()
|
r, w = self.pipe()
|
||||||
self.addCleanup(os.close, r)
|
code = textwrap.dedent(r"""
|
||||||
self.addCleanup(os.close, w)
|
|
||||||
code = r"""if 1:
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
|
@ -953,7 +959,7 @@ class SubinterpThreadingTests(BaseTestCase):
|
||||||
|
|
||||||
threading.Thread(target=f).start()
|
threading.Thread(target=f).start()
|
||||||
random_sleep()
|
random_sleep()
|
||||||
""" % (w,)
|
""" % (w,))
|
||||||
ret = test.support.run_in_subinterp(code)
|
ret = test.support.run_in_subinterp(code)
|
||||||
self.assertEqual(ret, 0)
|
self.assertEqual(ret, 0)
|
||||||
# The thread was joined properly.
|
# The thread was joined properly.
|
||||||
|
@ -964,10 +970,8 @@ class SubinterpThreadingTests(BaseTestCase):
|
||||||
# Python code returned but before the thread state is deleted.
|
# Python code returned but before the thread state is deleted.
|
||||||
# To achieve this, we register a thread-local object which sleeps
|
# To achieve this, we register a thread-local object which sleeps
|
||||||
# a bit when deallocated.
|
# a bit when deallocated.
|
||||||
r, w = os.pipe()
|
r, w = self.pipe()
|
||||||
self.addCleanup(os.close, r)
|
code = textwrap.dedent(r"""
|
||||||
self.addCleanup(os.close, w)
|
|
||||||
code = r"""if 1:
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import threading
|
import threading
|
||||||
|
@ -992,34 +996,38 @@ class SubinterpThreadingTests(BaseTestCase):
|
||||||
|
|
||||||
threading.Thread(target=f).start()
|
threading.Thread(target=f).start()
|
||||||
random_sleep()
|
random_sleep()
|
||||||
""" % (w,)
|
""" % (w,))
|
||||||
ret = test.support.run_in_subinterp(code)
|
ret = test.support.run_in_subinterp(code)
|
||||||
self.assertEqual(ret, 0)
|
self.assertEqual(ret, 0)
|
||||||
# The thread was joined properly.
|
# The thread was joined properly.
|
||||||
self.assertEqual(os.read(r, 1), b"x")
|
self.assertEqual(os.read(r, 1), b"x")
|
||||||
|
|
||||||
@cpython_only
|
def test_daemon_thread(self):
|
||||||
def test_daemon_threads_fatal_error(self):
|
r, w = self.pipe()
|
||||||
subinterp_code = r"""if 1:
|
code = textwrap.dedent(f"""
|
||||||
import os
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import sys
|
||||||
|
|
||||||
def f():
|
channel = open({w}, "w", closefd=False)
|
||||||
# Make sure the daemon thread is still running when
|
|
||||||
# Py_EndInterpreter is called.
|
|
||||||
time.sleep(10)
|
|
||||||
threading.Thread(target=f, daemon=True).start()
|
|
||||||
"""
|
|
||||||
script = r"""if 1:
|
|
||||||
import _testcapi
|
|
||||||
|
|
||||||
_testcapi.run_in_subinterp(%r)
|
def func():
|
||||||
""" % (subinterp_code,)
|
pass
|
||||||
with test.support.SuppressCrashReport():
|
|
||||||
rc, out, err = assert_python_failure("-c", script)
|
thread = threading.Thread(target=func, daemon=True)
|
||||||
self.assertIn("Fatal Python error: Py_EndInterpreter: "
|
try:
|
||||||
"not the last thread", err.decode())
|
thread.start()
|
||||||
|
except RuntimeError as exc:
|
||||||
|
print("ok: %s" % exc, file=channel, flush=True)
|
||||||
|
else:
|
||||||
|
thread.join()
|
||||||
|
print("fail: RuntimeError not raised", file=channel, flush=True)
|
||||||
|
""")
|
||||||
|
ret = test.support.run_in_subinterp(code)
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
|
||||||
|
msg = os.read(r, 100).decode().rstrip()
|
||||||
|
self.assertEqual("ok: daemon thread are not supported "
|
||||||
|
"in subinterpreters", msg)
|
||||||
|
|
||||||
|
|
||||||
class ThreadingExceptionTests(BaseTestCase):
|
class ThreadingExceptionTests(BaseTestCase):
|
||||||
|
|
|
@ -34,6 +34,7 @@ _start_new_thread = _thread.start_new_thread
|
||||||
_allocate_lock = _thread.allocate_lock
|
_allocate_lock = _thread.allocate_lock
|
||||||
_set_sentinel = _thread._set_sentinel
|
_set_sentinel = _thread._set_sentinel
|
||||||
get_ident = _thread.get_ident
|
get_ident = _thread.get_ident
|
||||||
|
_is_main_interpreter = _thread._is_main_interpreter
|
||||||
try:
|
try:
|
||||||
get_native_id = _thread.get_native_id
|
get_native_id = _thread.get_native_id
|
||||||
_HAVE_THREAD_NATIVE_ID = True
|
_HAVE_THREAD_NATIVE_ID = True
|
||||||
|
@ -846,6 +847,11 @@ class Thread:
|
||||||
|
|
||||||
if self._started.is_set():
|
if self._started.is_set():
|
||||||
raise RuntimeError("threads can only be started once")
|
raise RuntimeError("threads can only be started once")
|
||||||
|
|
||||||
|
if self.daemon and not _is_main_interpreter():
|
||||||
|
raise RuntimeError("daemon thread are not supported "
|
||||||
|
"in subinterpreters")
|
||||||
|
|
||||||
with _active_limbo_lock:
|
with _active_limbo_lock:
|
||||||
_limbo[self] = self
|
_limbo[self] = self
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
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.
|
|
@ -8,6 +8,14 @@
|
||||||
#include "structmember.h" /* offsetof */
|
#include "structmember.h" /* offsetof */
|
||||||
#include "pythread.h"
|
#include "pythread.h"
|
||||||
|
|
||||||
|
#include "clinic/_threadmodule.c.h"
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
module _thread
|
||||||
|
[clinic start generated code]*/
|
||||||
|
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=be8dbe5cc4b16df7]*/
|
||||||
|
|
||||||
|
|
||||||
static PyObject *ThreadError;
|
static PyObject *ThreadError;
|
||||||
static PyObject *str_dict;
|
static PyObject *str_dict;
|
||||||
|
|
||||||
|
@ -1442,6 +1450,21 @@ PyDoc_STRVAR(excepthook_doc,
|
||||||
\n\
|
\n\
|
||||||
Handle uncaught Thread.run() exception.");
|
Handle uncaught Thread.run() exception.");
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_thread._is_main_interpreter
|
||||||
|
|
||||||
|
Return True if the current interpreter is the main Python interpreter.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_thread__is_main_interpreter_impl(PyObject *module)
|
||||||
|
/*[clinic end generated code: output=7dd82e1728339adc input=cc1eb00fd4598915]*/
|
||||||
|
{
|
||||||
|
_PyRuntimeState *runtime = &_PyRuntime;
|
||||||
|
PyInterpreterState *interp = _PyRuntimeState_GetThreadState(runtime)->interp;
|
||||||
|
return PyBool_FromLong(interp == runtime->interpreters.main);
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef thread_methods[] = {
|
static PyMethodDef thread_methods[] = {
|
||||||
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
|
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
|
||||||
METH_VARARGS, start_new_doc},
|
METH_VARARGS, start_new_doc},
|
||||||
|
@ -1471,6 +1494,7 @@ static PyMethodDef thread_methods[] = {
|
||||||
METH_NOARGS, _set_sentinel_doc},
|
METH_NOARGS, _set_sentinel_doc},
|
||||||
{"_excepthook", thread_excepthook,
|
{"_excepthook", thread_excepthook,
|
||||||
METH_O, excepthook_doc},
|
METH_O, excepthook_doc},
|
||||||
|
_THREAD__IS_MAIN_INTERPRETER_METHODDEF
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
22
Modules/clinic/_threadmodule.c.h
generated
Normal file
22
Modules/clinic/_threadmodule.c.h
generated
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*[clinic input]
|
||||||
|
preserve
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_thread__is_main_interpreter__doc__,
|
||||||
|
"_is_main_interpreter($module, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Return True if the current interpreter is the main Python interpreter.");
|
||||||
|
|
||||||
|
#define _THREAD__IS_MAIN_INTERPRETER_METHODDEF \
|
||||||
|
{"_is_main_interpreter", (PyCFunction)_thread__is_main_interpreter, METH_NOARGS, _thread__is_main_interpreter__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_thread__is_main_interpreter_impl(PyObject *module);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_thread__is_main_interpreter(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return _thread__is_main_interpreter_impl(module);
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=505840d1b9101789 input=a9049054013a1b77]*/
|
Loading…
Add table
Add a link
Reference in a new issue