From: Serhiy Storchaka Date: Mon, 12 Jan 2026 10:05:09 +0000 (+0200) Subject: [3.13] gh-142881: Fix concurrent and reentrant call of atexit.unregister() (GH-142901... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1822f59dc783bc11ed478b78d822537a0425cf74;p=thirdparty%2FPython%2Fcpython.git [3.13] gh-142881: Fix concurrent and reentrant call of atexit.unregister() (GH-142901) (GH-143722) (cherry picked from commit dbd10a6c29ba1cfc9348924a090b5dc514470002) --- diff --git a/Lib/test/_test_atexit.py b/Lib/test/_test_atexit.py index 490b0686a0c1..2e961d6a4854 100644 --- a/Lib/test/_test_atexit.py +++ b/Lib/test/_test_atexit.py @@ -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() 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 000000000000..02f22d367bd8 --- /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 93d4c4ede325..23fcc1b9d35d 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);