From 51a185e5eea3d4c6c0c4d9abe543bada24727dbd Mon Sep 17 00:00:00 2001 From: Brij Date: Sat, 28 Feb 2026 13:33:56 -0500 Subject: [PATCH 1/6] Fix data race for list capacity --- Lib/test/test_list.py | 25 +++++++++++++++++++++++++ Objects/listobject.c | 10 ++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 642b54d34849da..ae685ab71d5061 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -6,6 +6,7 @@ from test.support.import_helper import import_module from test.support.script_helper import assert_python_failure, assert_python_ok import pickle +from threading import Thread import unittest class ListTest(list_tests.CommonTest): @@ -381,6 +382,30 @@ def foo(x): self.assertEqual(foo(list(range(10))), 45) + # gh-145036: race condition with list.__sizeof__() + def test_list_sizeof_free_threaded_build(self): + L = [] + + def test1(): + for _ in range(100): + L.append(1) + L.pop() + + def test2(): + for _ in range(100): + L.__sizeof__() + + threads = [] + for _ in range(4): + threads.append(Thread(target=test1)) + threads.append(Thread(target=test2)) + + for t in threads: + t.start() + for t in threads: + t.join() + + self.assertEqual(len(L), 0) if __name__ == "__main__": unittest.main() diff --git a/Objects/listobject.c b/Objects/listobject.c index 4a98c8e54ab03f..c472543dc6a74d 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3558,8 +3558,14 @@ list___sizeof___impl(PyListObject *self) /*[clinic end generated code: output=3417541f95f9a53e input=b8030a5d5ce8a187]*/ { size_t res = _PyObject_SIZE(Py_TYPE(self)); - Py_ssize_t allocated = FT_ATOMIC_LOAD_SSIZE_RELAXED(self->allocated); - res += (size_t)allocated * sizeof(void*); +#ifdef Py_GIL_DISABLED + PyObject **ob_item = _Py_atomic_load_ptr(&self->ob_item); + if (ob_item != NULL) { + res += list_capacity(ob_item) * sizeof(PyObject *); + } +#else + res += (size_t)self->allocated * sizeof(PyObject *); +#endif return PyLong_FromSize_t(res); } From ffe1efb7a43ca285a3fc1fbb0e9cd7444d483a43 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:42:37 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst new file mode 100644 index 00000000000000..271d2c4a08aa95 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst @@ -0,0 +1 @@ +In free-threaded build, no longer change of race condition when calling :meth:`__sizeof__` on a :class:`list` From 9a5991e7c30f620f8299e2bc124687683ca3ccd4 Mon Sep 17 00:00:00 2001 From: bkap123 <97006829+bkap123@users.noreply.github.com> Date: Sun, 1 Mar 2026 07:12:47 -0500 Subject: [PATCH 3/6] Update Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst Co-authored-by: Kumar Aditya --- .../2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst index 271d2c4a08aa95..f6da87c616526e 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst @@ -1 +1 @@ -In free-threaded build, no longer change of race condition when calling :meth:`__sizeof__` on a :class:`list` +In free-threaded build, no longer change of race condition when calling :meth:`!__sizeof__` on a :class:`list` From 8f0aeed8038aa703702bc76cd87773fb66d4320f Mon Sep 17 00:00:00 2001 From: Brij Date: Mon, 2 Mar 2026 21:06:57 -0500 Subject: [PATCH 4/6] move test --- Lib/test/test_free_threading/test_list.py | 25 ++++++++++++++++++++++ Lib/test/test_list.py | 26 ----------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_free_threading/test_list.py b/Lib/test/test_free_threading/test_list.py index 27ddc9c2d26dfb..4a8bde246a6d6d 100644 --- a/Lib/test/test_free_threading/test_list.py +++ b/Lib/test/test_free_threading/test_list.py @@ -149,6 +149,31 @@ def reader_list(b, l): with threading_helper.start_threads(threads): pass + # gh-145036: race condition with list.__sizeof__() + def test_list_sizeof_free_threaded_build(self): + L = [] + + def test1(): + for _ in range(100): + L.append(1) + L.pop() + + def test2(): + for _ in range(100): + L.__sizeof__() + + threads = [] + for _ in range(4): + threads.append(Thread(target=test1)) + threads.append(Thread(target=test2)) + + for t in threads: + t.start() + for t in threads: + t.join() + + self.assertEqual(len(L), 0) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index ae685ab71d5061..621c2e55fe061a 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -6,7 +6,6 @@ from test.support.import_helper import import_module from test.support.script_helper import assert_python_failure, assert_python_ok import pickle -from threading import Thread import unittest class ListTest(list_tests.CommonTest): @@ -382,30 +381,5 @@ def foo(x): self.assertEqual(foo(list(range(10))), 45) - # gh-145036: race condition with list.__sizeof__() - def test_list_sizeof_free_threaded_build(self): - L = [] - - def test1(): - for _ in range(100): - L.append(1) - L.pop() - - def test2(): - for _ in range(100): - L.__sizeof__() - - threads = [] - for _ in range(4): - threads.append(Thread(target=test1)) - threads.append(Thread(target=test2)) - - for t in threads: - t.start() - for t in threads: - t.join() - - self.assertEqual(len(L), 0) - if __name__ == "__main__": unittest.main() From e879c6ef02adedfcebe820546228e0d1c1315930 Mon Sep 17 00:00:00 2001 From: bkap123 <97006829+bkap123@users.noreply.github.com> Date: Tue, 3 Mar 2026 05:06:12 -0500 Subject: [PATCH 5/6] Update Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst Co-authored-by: Kumar Aditya --- .../2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst index f6da87c616526e..2a565c1d02bc2e 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-28-18-42-36.gh-issue-145036.70Kbfz.rst @@ -1 +1 @@ -In free-threaded build, no longer change of race condition when calling :meth:`!__sizeof__` on a :class:`list` +In free-threaded build, fix race condition when calling :meth:`!__sizeof__` on a :class:`list` From 06e877dbc5cbe9030e73c613ca4ffd4e9ff8f1e0 Mon Sep 17 00:00:00 2001 From: Brij Date: Tue, 3 Mar 2026 05:23:07 -0500 Subject: [PATCH 6/6] quick changes --- Lib/test/test_free_threading/test_list.py | 16 ++++++---------- Lib/test/test_list.py | 1 + 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_free_threading/test_list.py b/Lib/test/test_free_threading/test_list.py index 4a8bde246a6d6d..0ede4df103f728 100644 --- a/Lib/test/test_free_threading/test_list.py +++ b/Lib/test/test_free_threading/test_list.py @@ -153,26 +153,22 @@ def reader_list(b, l): def test_list_sizeof_free_threaded_build(self): L = [] - def test1(): + def mutate_function(): for _ in range(100): L.append(1) L.pop() - def test2(): + def size_function(): for _ in range(100): L.__sizeof__() threads = [] for _ in range(4): - threads.append(Thread(target=test1)) - threads.append(Thread(target=test2)) + threads.append(Thread(target=mutate_function)) + threads.append(Thread(target=size_function)) - for t in threads: - t.start() - for t in threads: - t.join() - - self.assertEqual(len(L), 0) + with threading_helper.start_threads(threads): + pass if __name__ == "__main__": diff --git a/Lib/test/test_list.py b/Lib/test/test_list.py index 621c2e55fe061a..642b54d34849da 100644 --- a/Lib/test/test_list.py +++ b/Lib/test/test_list.py @@ -381,5 +381,6 @@ def foo(x): self.assertEqual(foo(list(range(10))), 45) + if __name__ == "__main__": unittest.main()