From a1af6886619a9b69d2136421974aada6b56ad0b5 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 9 Mar 2026 22:30:02 +0100 Subject: [PATCH 1/6] fix: make bytearray.resize thread-safe --- Objects/bytearrayobject.c | 5 +++-- Objects/clinic/bytearrayobject.c.h | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 7f09769e12f05f..e2fea94e099626 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -1506,6 +1506,7 @@ bytearray_removesuffix_impl(PyByteArrayObject *self, Py_buffer *suffix) /*[clinic input] +@critical_section bytearray.resize size: Py_ssize_t New size to resize to. @@ -1515,10 +1516,10 @@ Resize the internal buffer of bytearray to len. static PyObject * bytearray_resize_impl(PyByteArrayObject *self, Py_ssize_t size) -/*[clinic end generated code: output=f73524922990b2d9 input=6c9a260ca7f72071]*/ +/*[clinic end generated code: output=f73524922990b2d9 input=116046316a2b5cfc]*/ { Py_ssize_t start_size = PyByteArray_GET_SIZE(self); - int result = PyByteArray_Resize((PyObject *)self, size); + int result = bytearray_resize_lock_held((PyObject *)self, size); if (result < 0) { return NULL; } diff --git a/Objects/clinic/bytearrayobject.c.h b/Objects/clinic/bytearrayobject.c.h index be704ccf68f669..cf60d0ceadc7d1 100644 --- a/Objects/clinic/bytearrayobject.c.h +++ b/Objects/clinic/bytearrayobject.c.h @@ -625,7 +625,9 @@ bytearray_resize(PyObject *self, PyObject *arg) } size = ival; } + Py_BEGIN_CRITICAL_SECTION(self); return_value = bytearray_resize_impl((PyByteArrayObject *)self, size); + Py_END_CRITICAL_SECTION(); exit: return return_value; @@ -1833,4 +1835,4 @@ bytearray_sizeof(PyObject *self, PyObject *Py_UNUSED(ignored)) { return bytearray_sizeof_impl((PyByteArrayObject *)self); } -/*[clinic end generated code: output=5eddefde2a001ceb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=2d76ef023928424f input=a9049054013a1b77]*/ From 2511674febbf1f7537d4f535a92bd8e3cd0b1681 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Mon, 9 Mar 2026 23:08:46 +0100 Subject: [PATCH 2/6] chore: add news entry --- .../2026-03-09-00-00-00.gh-issue-145713.KR6azvzI.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-00-00-00.gh-issue-145713.KR6azvzI.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-00-00-00.gh-issue-145713.KR6azvzI.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-00-00-00.gh-issue-145713.KR6azvzI.rst new file mode 100644 index 00000000000000..2cf83eff31056a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-09-00-00-00.gh-issue-145713.KR6azvzI.rst @@ -0,0 +1,3 @@ +Make :meth:`bytearray.resize` thread-safe in the free-threaded build by +using a critical section and calling the lock-held variant of the resize +function. From 1ac4fd9b416e2c86d3ee540b203f6d374fbed003 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Tue, 10 Mar 2026 08:33:10 +0100 Subject: [PATCH 3/6] test: add a regression test for bytearray.resize thread safety --- Lib/test/test_bytes.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 1c64bf888f9d27..d2819b6784ef98 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2908,6 +2908,19 @@ def check(funcs, it): check([iter_next] + [iter_reduce] * 10, iter(ba)) # for tsan check([iter_next] + [iter_setstate] * 10, iter(ba)) # for tsan + def test_free_threading_bytearray_resize(self): + def resize_stress(ba): + for _ in range(100_000): + try: + ba.resize(10_000) + ba.resize(1) + except (BufferError, ValueError): + pass + + ba = bytearray(100) + threads = [threading.Thread(target=resize_stress, args=(ba,)) for _ in range(4)] + for t in threads: t.start() + for t in threads: t.join() if __name__ == "__main__": unittest.main() From a731e8c82368f5df44a31304ef3cbb4f768dd3d9 Mon Sep 17 00:00:00 2001 From: Thomas Kowalski Date: Tue, 10 Mar 2026 11:43:07 +0100 Subject: [PATCH 4/6] test: update test based on review --- Lib/test/test_bytes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index d2819b6784ef98..ca7219a3e6d3d4 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2908,6 +2908,9 @@ def check(funcs, it): check([iter_next] + [iter_reduce] * 10, iter(ba)) # for tsan check([iter_next] + [iter_setstate] * 10, iter(ba)) # for tsan + @unittest.skipUnless(support.Py_GIL_DISABLED, 'this test can only possibly fail with GIL disabled') + @threading_helper.reap_threads + @threading_helper.requires_working_threading() def test_free_threading_bytearray_resize(self): def resize_stress(ba): for _ in range(100_000): @@ -2919,8 +2922,8 @@ def resize_stress(ba): ba = bytearray(100) threads = [threading.Thread(target=resize_stress, args=(ba,)) for _ in range(4)] - for t in threads: t.start() - for t in threads: t.join() + with threading_helper.start_threads(threads): + pass if __name__ == "__main__": unittest.main() From 16a80f885864b77c57549c3a4c57c6a257bb900a Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Tue, 10 Mar 2026 18:01:43 +0530 Subject: [PATCH 5/6] Apply suggestion from @kumaraditya303 --- Lib/test/test_bytes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index ca7219a3e6d3d4..84cbb002e4089a 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2913,7 +2913,7 @@ def check(funcs, it): @threading_helper.requires_working_threading() def test_free_threading_bytearray_resize(self): def resize_stress(ba): - for _ in range(100_000): + for _ in range(1000): try: ba.resize(10_000) ba.resize(1) From 12e2ad6f4ead6afc17c6a511f5ea56b2ce1ef6f1 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Tue, 10 Mar 2026 18:02:04 +0530 Subject: [PATCH 6/6] Apply suggestion from @kumaraditya303 --- Lib/test/test_bytes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index 84cbb002e4089a..876ecd4467b0a2 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -2915,7 +2915,7 @@ def test_free_threading_bytearray_resize(self): def resize_stress(ba): for _ in range(1000): try: - ba.resize(10_000) + ba.resize(1000) ba.resize(1) except (BufferError, ValueError): pass