base class constructor (``Thread.__init__()``) before doing anything else to
the thread.
+ Daemon threads must not be used in subinterpreters.
+
.. versionchanged:: 3.3
Added the *daemon* argument.
This method will raise a :exc:`RuntimeError` if called more than once
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 representing the thread's activity.
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
=============
else:
global _interrupt
_interrupt = True
+
+
+def _is_main_interpreter():
+ return True
import os
import subprocess
import signal
+import textwrap
from test import lock_tests
from test import support
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):
# Non-daemon threads should be joined at subinterpreter shutdown
# (issue #18808)
- r, w = os.pipe()
- self.addCleanup(os.close, r)
- self.addCleanup(os.close, w)
- code = r"""if 1:
+ r, w = self.pipe()
+ code = textwrap.dedent(r"""
import os
import random
import threading
threading.Thread(target=f).start()
random_sleep()
- """ % (w,)
+ """ % (w,))
ret = test.support.run_in_subinterp(code)
self.assertEqual(ret, 0)
# The thread was joined properly.
# Python code returned but before the thread state is deleted.
# To achieve this, we register a thread-local object which sleeps
# a bit when deallocated.
- r, w = os.pipe()
- self.addCleanup(os.close, r)
- self.addCleanup(os.close, w)
- code = r"""if 1:
+ r, w = self.pipe()
+ code = textwrap.dedent(r"""
import os
import random
import threading
threading.Thread(target=f).start()
random_sleep()
- """ % (w,)
+ """ % (w,))
ret = test.support.run_in_subinterp(code)
self.assertEqual(ret, 0)
# The thread was joined properly.
self.assertEqual(os.read(r, 1), b"x")
- @cpython_only
- def test_daemon_threads_fatal_error(self):
- subinterp_code = r"""if 1:
- import os
+ def test_daemon_thread(self):
+ r, w = self.pipe()
+ code = textwrap.dedent(f"""
import threading
- import time
+ import sys
- def f():
- # 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
+ channel = open({w}, "w", closefd=False)
+
+ def func():
+ pass
+
+ thread = threading.Thread(target=func, daemon=True)
+ try:
+ 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)
- _testcapi.run_in_subinterp(%r)
- """ % (subinterp_code,)
- with test.support.SuppressCrashReport():
- rc, out, err = assert_python_failure("-c", script)
- self.assertIn("Fatal Python error: Py_EndInterpreter: "
- "not the last thread", err.decode())
+ msg = os.read(r, 100).decode().rstrip()
+ self.assertEqual("ok: daemon thread are not supported "
+ "in subinterpreters", msg)
class ThreadingExceptionTests(BaseTestCase):
_allocate_lock = _thread.allocate_lock
_set_sentinel = _thread._set_sentinel
get_ident = _thread.get_ident
+_is_main_interpreter = _thread._is_main_interpreter
try:
get_native_id = _thread.get_native_id
_HAVE_THREAD_NATIVE_ID = True
if self._started.is_set():
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:
_limbo[self] = self
try:
--- /dev/null
+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.
#include "structmember.h" /* offsetof */
#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 *str_dict;
\n\
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[] = {
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
METH_VARARGS, start_new_doc},
METH_NOARGS, _set_sentinel_doc},
{"_excepthook", thread_excepthook,
METH_O, excepthook_doc},
+ _THREAD__IS_MAIN_INTERPRETER_METHODDEF
{NULL, NULL} /* sentinel */
};
--- /dev/null
+/*[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]*/