Skip to content

Commit 1822f59

Browse files
[3.13] gh-142881: Fix concurrent and reentrant call of atexit.unregister() (GH-142901) (GH-143722)
(cherry picked from commit dbd10a6)
1 parent 8ec1083 commit 1822f59

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

Lib/test/_test_atexit.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,40 @@ def __eq__(self, other):
148148
atexit.unregister(Evil())
149149
atexit._clear()
150150

151+
def test_eq_unregister(self):
152+
# Issue #112127: callback's __eq__ may call unregister
153+
def f1():
154+
log.append(1)
155+
def f2():
156+
log.append(2)
157+
def f3():
158+
log.append(3)
159+
160+
class Pred:
161+
def __eq__(self, other):
162+
nonlocal cnt
163+
cnt += 1
164+
if cnt == when:
165+
atexit.unregister(what)
166+
if other is f2:
167+
return True
168+
return False
169+
170+
for what, expected in (
171+
(f1, [3]),
172+
(f2, [3, 1]),
173+
(f3, [1]),
174+
):
175+
for when in range(1, 4):
176+
with self.subTest(what=what.__name__, when=when):
177+
cnt = 0
178+
log = []
179+
for f in (f1, f2, f3):
180+
atexit.register(f)
181+
atexit.unregister(Pred())
182+
atexit._run_exitfuncs()
183+
self.assertEqual(log, expected)
184+
151185

152186
if __name__ == "__main__":
153187
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix concurrent and reentrant call of :func:`atexit.unregister`.

Modules/atexitmodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ static void
5757
atexit_delete_cb(struct atexit_state *state, int i)
5858
{
5959
atexit_py_callback *cb = state->callbacks[i];
60+
if (cb == NULL) {
61+
return;
62+
}
6063
state->callbacks[i] = NULL;
6164

6265
Py_DECREF(cb->func);

0 commit comments

Comments
 (0)