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/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..f6da87c616526e --- /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` 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); }