mirror of
https://github.com/python/cpython.git
synced 2026-01-26 12:55:08 +00:00
gh-142881: Fix concurrent and reentrant call of atexit.unregister() (GH-142901)
This commit is contained in:
parent
5f28aa2f37
commit
dbd10a6c29
@ -148,6 +148,40 @@ class GeneralTest(unittest.TestCase):
|
||||
atexit.unregister(Evil())
|
||||
atexit._clear()
|
||||
|
||||
def test_eq_unregister(self):
|
||||
# Issue #112127: callback's __eq__ may call unregister
|
||||
def f1():
|
||||
log.append(1)
|
||||
def f2():
|
||||
log.append(2)
|
||||
def f3():
|
||||
log.append(3)
|
||||
|
||||
class Pred:
|
||||
def __eq__(self, other):
|
||||
nonlocal cnt
|
||||
cnt += 1
|
||||
if cnt == when:
|
||||
atexit.unregister(what)
|
||||
if other is f2:
|
||||
return True
|
||||
return False
|
||||
|
||||
for what, expected in (
|
||||
(f1, [3]),
|
||||
(f2, [3, 1]),
|
||||
(f3, [1]),
|
||||
):
|
||||
for when in range(1, 4):
|
||||
with self.subTest(what=what.__name__, when=when):
|
||||
cnt = 0
|
||||
log = []
|
||||
for f in (f1, f2, f3):
|
||||
atexit.register(f)
|
||||
atexit.unregister(Pred())
|
||||
atexit._run_exitfuncs()
|
||||
self.assertEqual(log, expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -0,0 +1 @@
|
||||
Fix concurrent and reentrant call of :func:`atexit.unregister`.
|
||||
@ -256,22 +256,36 @@ atexit_ncallbacks(PyObject *module, PyObject *Py_UNUSED(dummy))
|
||||
static int
|
||||
atexit_unregister_locked(PyObject *callbacks, PyObject *func)
|
||||
{
|
||||
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(callbacks); ++i) {
|
||||
for (Py_ssize_t i = PyList_GET_SIZE(callbacks) - 1; i >= 0; --i) {
|
||||
PyObject *tuple = Py_NewRef(PyList_GET_ITEM(callbacks, i));
|
||||
assert(PyTuple_CheckExact(tuple));
|
||||
PyObject *to_compare = PyTuple_GET_ITEM(tuple, 0);
|
||||
int cmp = PyObject_RichCompareBool(func, to_compare, Py_EQ);
|
||||
Py_DECREF(tuple);
|
||||
if (cmp < 0)
|
||||
{
|
||||
if (cmp < 0) {
|
||||
Py_DECREF(tuple);
|
||||
return -1;
|
||||
}
|
||||
if (cmp == 1) {
|
||||
// We found a callback!
|
||||
if (PyList_SetSlice(callbacks, i, i + 1, NULL) < 0) {
|
||||
return -1;
|
||||
// But its index could have changed if it or other callbacks were
|
||||
// unregistered during the comparison.
|
||||
Py_ssize_t j = PyList_GET_SIZE(callbacks) - 1;
|
||||
j = Py_MIN(j, i);
|
||||
for (; j >= 0; --j) {
|
||||
if (PyList_GET_ITEM(callbacks, j) == tuple) {
|
||||
// We found the callback index! For real!
|
||||
if (PyList_SetSlice(callbacks, j, j + 1, NULL) < 0) {
|
||||
Py_DECREF(tuple);
|
||||
return -1;
|
||||
}
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
--i;
|
||||
}
|
||||
Py_DECREF(tuple);
|
||||
if (i >= PyList_GET_SIZE(callbacks)) {
|
||||
i = PyList_GET_SIZE(callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user