https://github.com/python/cpython/commit/1822f59dc783bc11ed478b78d822537a0425cf74
commit: 1822f59dc783bc11ed478b78d822537a0425cf74
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-01-12T10:05:09Z
summary:

[3.13] gh-142881: Fix concurrent and reentrant call of atexit.unregister() 
(GH-142901) (GH-143722)

(cherry picked from commit dbd10a6c29ba1cfc9348924a090b5dc514470002)

files:
A Misc/NEWS.d/next/Library/2025-12-17-20-18-17.gh-issue-142881.5IizIQ.rst
M Lib/test/_test_atexit.py
M Modules/atexitmodule.c

diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py
index 490b0686a0c179..2e961d6a4854a0 100644
--- a/Lib/test/_test_atexit.py
+++ b/Lib/test/_test_atexit.py
@@ -148,6 +148,40 @@ def __eq__(self, other):
                 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()
diff --git 
a/Misc/NEWS.d/next/Library/2025-12-17-20-18-17.gh-issue-142881.5IizIQ.rst 
b/Misc/NEWS.d/next/Library/2025-12-17-20-18-17.gh-issue-142881.5IizIQ.rst
new file mode 100644
index 00000000000000..02f22d367bd831
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-17-20-18-17.gh-issue-142881.5IizIQ.rst
@@ -0,0 +1 @@
+Fix concurrent and reentrant call of :func:`atexit.unregister`.
diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c
index 93d4c4ede32513..23fcc1b9d35dc0 100644
--- a/Modules/atexitmodule.c
+++ b/Modules/atexitmodule.c
@@ -57,6 +57,9 @@ static void
 atexit_delete_cb(struct atexit_state *state, int i)
 {
     atexit_py_callback *cb = state->callbacks[i];
+    if (cb == NULL) {
+        return;
+    }
     state->callbacks[i] = NULL;
 
     Py_DECREF(cb->func);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to