Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ typedef struct _PyThreadStateImpl {
// Reused for the thread's lifetime; owns placeholders while they resolve.
PyObject *lazy_imports;

// Held while this thread owns the warnings lock.
PyInterpreterGuard *warnings_lock_guard;

// Distinguishes between yield and return from PyEval_EvalFrame().
// See gen_send_ex2() in Objects/genobject.c
enum {
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,49 @@ def test_datetime_reset_strptime(self):
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
self.assertEqual(out, '20000101\n' * INIT_LOOPS)

@threading_helper.requires_working_threading()
def test_warnings_at_shutdown(self):
code = textwrap.dedent('''
import threading
import time
import warnings

warnings.simplefilter('always', ResourceWarning)
ready = threading.Event()

class WarnOnDelete:
def __init__(self):
self.self_ref = self

def __del__(self):
warnings.warn('late', ResourceWarning)

def showwarning(message, *args, **kwargs):
if str(message) == 'hold':
warnings.warn('nested', ResourceWarning)
ready.set()
# A Python event cannot synchronize with the point where
# shutdown stops daemon threads, so hold the lock briefly.
time.sleep(0.1)
else:
print(message)

warnings.showwarning = showwarning

def worker():
warnings.warn('hold', ResourceWarning)

threading.Thread(target=worker, daemon=True).start()
assert ready.wait(4), 'daemon did not start warning'
# Collect the cycle during shutdown GC, after daemon threads stop.
WarnOnDelete()
''')
out, err = self.run_embedded_interpreter(
"test_repeated_init_exec", code, "pass",
env=remove_python_envvars(), timeout=10)
self.assertEqual(out, 'nested\nlate\n')
self.assertEqual(err, '--- Loop #1 ---\n--- Loop #2 ---\n')

def test_static_types_inherited_slots(self):
script = textwrap.dedent("""
import test.support
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Prevent a shutdown deadlock when a daemon thread emits a warning. The
interpreter now waits for the thread to release the warnings lock.
75 changes: 57 additions & 18 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
#include "pycore_frame.h" // PyFrameObject
#include "pycore_genobject.h" // PyAsyncGenObject
#include "pycore_import.h" // _PyImport_GetModules()
#include "pycore_interp.h" // _PyInterpreterState_GetFinalizing()
#include "pycore_interpframe.h" // _PyFrame_GetCode()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_pylifecycle.h" // _Py_IsInterpreterFinalizing()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_tstate.h" // _PyThreadStateImpl
#include "pycore_traceback.h" // _Py_DisplaySourceLine()
#include "pycore_tuple.h" // _PyTuple_FromPair
#include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
Expand Down Expand Up @@ -254,20 +256,47 @@ get_warnings_attr(PyInterpreterState *interp, PyObject *attr, int try_import)
return obj;
}

static inline void
static inline int
warnings_lock(PyInterpreterState *interp)
{
WarningsState *st = warnings_get_state(interp);
assert(st != NULL);
PyThreadState *tstate = _PyThreadState_GET();
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
if (!_PyRecursiveMutex_IsLockedByCurrentThread(&st->lock) &&
_PyInterpreterState_GetFinalizing(interp) != tstate)
{
assert(tstate_impl->warnings_lock_guard == NULL);
PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent();
if (guard == NULL) {
return -1;
}
tstate_impl->warnings_lock_guard = guard;
}
_PyRecursiveMutex_Lock(&st->lock);
return 0;
}

static inline int
warnings_unlock(PyInterpreterState *interp)
{
WarningsState *st = warnings_get_state(interp);
assert(st != NULL);
return _PyRecursiveMutex_TryUnlock(&st->lock);
if (!_PyRecursiveMutex_IsLockedByCurrentThread(&st->lock)) {
return -1;
}
if (st->lock.level > 0) {
_PyRecursiveMutex_Unlock(&st->lock);
return 0;
}
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
PyInterpreterGuard *guard = tstate->warnings_lock_guard;
tstate->warnings_lock_guard = NULL;
_PyRecursiveMutex_Unlock(&st->lock);
if (guard != NULL) {
PyInterpreterGuard_Close(guard);
}
return 0;
}

static inline bool
Expand Down Expand Up @@ -351,7 +380,9 @@ warnings_acquire_lock_impl(PyObject *module)
if (interp == NULL) {
return NULL;
}
warnings_lock(interp);
if (warnings_lock(interp) < 0) {
return NULL;
}
Py_RETURN_NONE;
}

Expand Down Expand Up @@ -1133,10 +1164,14 @@ do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level,
&filename, &lineno, &module, &registry))
return NULL;

warnings_lock(tstate->interp);
res = warn_explicit(tstate, category, message, filename, lineno, module, registry,
NULL, source);
warnings_unlock(tstate->interp);
if (warnings_lock(tstate->interp) < 0) {
res = NULL;
}
else {
res = warn_explicit(tstate, category, message, filename, lineno, module, registry,
NULL, source);
warnings_unlock(tstate->interp);
}
Py_DECREF(filename);
Py_DECREF(registry);
Py_DECREF(module);
Expand Down Expand Up @@ -1315,10 +1350,14 @@ warnings_warn_explicit_impl(PyObject *module, PyObject *message,
}
}

warnings_lock(tstate->interp);
returned = warn_explicit(tstate, category, message, filename, lineno,
mod, registry, source_line, sourceobj);
warnings_unlock(tstate->interp);
if (warnings_lock(tstate->interp) < 0) {
returned = NULL;
}
else {
returned = warn_explicit(tstate, category, message, filename, lineno,
mod, registry, source_line, sourceobj);
warnings_unlock(tstate->interp);
}
Py_XDECREF(source_line);
return returned;
}
Expand Down Expand Up @@ -1465,7 +1504,9 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message,
return -1;
}

warnings_lock(tstate->interp);
if (warnings_lock(tstate->interp) < 0) {
return -1;
}
res = warn_explicit(tstate, category, message, filename, lineno,
module, registry, NULL, NULL);
warnings_unlock(tstate->interp);
Expand Down Expand Up @@ -1530,19 +1571,17 @@ PyErr_WarnExplicitFormat(PyObject *category,
va_start(vargs, format);
message = PyUnicode_FromFormatV(format, vargs);
if (message != NULL) {
PyObject *res;
PyThreadState *tstate = get_current_tstate();
if (tstate != NULL) {
warnings_lock(tstate->interp);
res = warn_explicit(tstate, category, message, filename, lineno,
module, registry, NULL, NULL);
if (tstate != NULL && warnings_lock(tstate->interp) == 0) {
PyObject *res = warn_explicit(tstate, category, message, filename, lineno,
module, registry, NULL, NULL);
warnings_unlock(tstate->interp);
Py_DECREF(message);
if (res != NULL) {
Py_DECREF(res);
ret = 0;
}
}
Py_DECREF(message);
}
va_end(vargs);
exit:
Expand Down
Loading