Skip to content

Commit 598efe0

Browse files
committed
gh-149211: Preserve custom warning handler during finalization
1 parent c83d3d7 commit 598efe0

5 files changed

Lines changed: 60 additions & 2 deletions

File tree

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ struct _warnings_runtime_state {
660660
PyObject *filters; /* List */
661661
PyObject *once_registry; /* Dict */
662662
PyObject *default_action; /* String */
663+
PyObject *module; /* Weakref, used after sys.modules cleanup */
663664
_PyRecursiveMutex lock;
664665
long filters_version;
665666
PyObject *context;

Lib/test/test_warnings/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,30 @@ def __del__(self):
16931693
self.assertEqual(err.decode().rstrip(),
16941694
'<string>:7: UserWarning: test')
16951695

1696+
def test_showwarning_during_finalization(self):
1697+
# gh-149211: warnings.warn() should use a custom showwarning()
1698+
# during Python finalization.
1699+
code = """
1700+
import sys
1701+
import warnings
1702+
1703+
def showwarning(message, category, filename, lineno,
1704+
file=None, line=None, stderr=sys.stderr):
1705+
print(f"custom showwarning: {message}", file=file or stderr)
1706+
1707+
warnings.showwarning = showwarning
1708+
1709+
class A:
1710+
def __del__(self):
1711+
warnings.warn("test")
1712+
1713+
a = A()
1714+
"""
1715+
rc, out, err = assert_python_ok("-c", code)
1716+
self.assertEqual(out, b"")
1717+
self.assertEqual(err.decode().rstrip(),
1718+
"custom showwarning: test")
1719+
16961720
def test_late_resource_warning(self):
16971721
# Issue #21925: Emitting a ResourceWarning late during the Python
16981722
# shutdown must be logged.

Lib/warnings.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
_filters_mutated_lock_held,
6969
_onceregistry as onceregistry,
7070
_release_lock,
71+
_set_module as _set_c_module,
7172
_warnings_context,
7273
filters,
7374
warn,
@@ -91,9 +92,13 @@ def __exit__(self, *args):
9192

9293
# Module initialization
9394
_set_module(sys.modules[__name__])
95+
if _warnings_defaults:
96+
_set_c_module(sys.modules[__name__])
9497
_processoptions(sys.warnoptions)
9598
if not _warnings_defaults:
9699
_setup_defaults()
97100

98101
del _warnings_defaults
102+
if "_set_c_module" in globals():
103+
del _set_c_module
99104
del _setup_defaults
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`warnings.warn` to call a custom :func:`warnings.showwarning`
2+
during Python finalization.

Python/_warnings.c

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ warnings_clear_state(WarningsState *st)
6969
Py_CLEAR(st->filters);
7070
Py_CLEAR(st->once_registry);
7171
Py_CLEAR(st->default_action);
72+
Py_CLEAR(st->module);
7273
Py_CLEAR(st->context);
7374
}
7475

@@ -245,8 +246,13 @@ get_warnings_attr(PyInterpreterState *interp, PyObject *attr, int try_import)
245246
return NULL;
246247
}
247248
warnings_module = PyImport_GetModule(&_Py_ID(warnings));
248-
if (warnings_module == NULL)
249-
return NULL;
249+
if (warnings_module == NULL) {
250+
WarningsState *st = warnings_get_state(interp);
251+
if (st->module == NULL ||
252+
PyWeakref_GetRef(st->module, &warnings_module) <= 0) {
253+
return NULL;
254+
}
255+
}
250256
}
251257

252258
(void)PyObject_GetOptionalAttr(warnings_module, attr, &obj);
@@ -375,6 +381,25 @@ warnings_release_lock_impl(PyObject *module)
375381
Py_RETURN_NONE;
376382
}
377383

384+
static PyObject *
385+
warnings_set_module(PyObject *Py_UNUSED(module), PyObject *warnings_module)
386+
{
387+
PyInterpreterState *interp = get_current_interp();
388+
if (interp == NULL) {
389+
return NULL;
390+
}
391+
WarningsState *st = warnings_get_state(interp);
392+
if (st == NULL) {
393+
return NULL;
394+
}
395+
PyObject *ref = PyWeakref_NewRef(warnings_module, NULL);
396+
if (ref == NULL) {
397+
return NULL;
398+
}
399+
Py_XSETREF(st->module, ref);
400+
Py_RETURN_NONE;
401+
}
402+
378403
static PyObject *
379404
get_once_registry(PyInterpreterState *interp)
380405
{
@@ -1593,6 +1618,7 @@ static PyMethodDef warnings_functions[] = {
15931618
WARNINGS_FILTERS_MUTATED_LOCK_HELD_METHODDEF
15941619
WARNINGS_ACQUIRE_LOCK_METHODDEF
15951620
WARNINGS_RELEASE_LOCK_METHODDEF
1621+
{"_set_module", warnings_set_module, METH_O, NULL},
15961622
/* XXX(brett.cannon): add showwarning? */
15971623
/* XXX(brett.cannon): Reasonable to add formatwarning? */
15981624
{NULL, NULL} /* sentinel */

0 commit comments

Comments
 (0)