Repository navigation
gh-157242: Fix PyBytesWriter_Resize() on MemoryError - #157243
Conversation
If bytearray.resize() or bytearray.take_bytes() fails, leave the bytearray unchanged. If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error.
|
It would be nice if the "guaranteed no global" case was also used for the |
serhiy-storchaka
left a comment
There was a problem hiding this comment.
No need to add a new parameter, it saves nothing. _PyBytes_Resize can be implemented via _PyBytes_ResizeKeepOnError.
I wonder if we can simply change the behavior of _PyBytes_Resize.
Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
For the in-place resize code path, no longer call _Py_ForgetReference() and _PyReftracerTrack() before PyObject_Realloc().
|
Please review the updated PR. I addressed reviews. @maurycy found a fix for the memmove() code path which worried me. I applied his suggestion and added a test.
Thanks for the advice. I reworked _PyBytes_Resize(): for the in-place resize code path, no longer call _Py_ForgetReference() and _PyReftracerTrack() before PyObject_Realloc(). Only call them on success. With this change, I was able to easy implement _PyBytes_Resize() with _PyBytes_ResizeKeepOnError(). |
Maybe Note: PR gh-156996 does fix PyBytes_FromStringAndSize() usage in bytearray. I don't try to replace this fix. |
|
bytearray.resize() and bytearray.take_bytes() have been fixed to no longer use a singleton: I merged main in my PR to get the PR gh-156996 fix. |
|
@maurycy: I modified resize() and take_bytes() to avoid memmove() usage if we would be unable to revert the bytesarray to its previous state on MemoryError. Does it look correct to you? I also added more tests injecting MemoryError. |
|
@vstinner: Thank you. I will take a look more carefully later today. |
|
@vstinner Two more findings. That's such a nice PR! Good news: the PR fixes this regression: 2026-09-10T17:44:57.890346000+0200 maurycy@gimel /Users/maurycy/work/cpython (main 9bd670c?) % ./python.exe
Python 3.16.0a0 (heads/main:9bd670cba21, Sep 10 2026, 17:37:43) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123")
... try:
... _testcapi.set_nomemory(0)
... del ba[1:-1]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
>>> len(ba)
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
len(ba)
~~~^^^^
SystemError: <built-in function len> returned NULL without setting an exception
>>> bytes(ba)
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
bytes(ba)
~~~~~^^^^
ValueError: size must be >= 0
>>>
2026-09-10T17:45:16.126365000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr157243 (pr157243 f51cba5?) % ./python.exe
Python 3.16.0a0 (heads/pr157243:f51cba57d3e, Sep 10 2026, 14:31:04) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123")
... try:
... _testcapi.set_nomemory(0)
... del ba[1:-1]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
>>> len(ba)
2
>>> bytes(ba)
b'a3'
>>> Perhaps we cover this with test, too? Bad news: 2026-09-10T17:47:19.394053000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr157243 (pr157243 f51cba5?) % ./python.exe
Python 3.16.0a0 (heads/pr157243:f51cba57d3e, Sep 10 2026, 14:31:04) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123456")
... ba += b"7"
...
... try:
... _testcapi.set_nomemory(0)
... del ba[::2]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
... ba
...
bytearray(b'b135734567')
>>>
2026-09-10T17:48:34.225959000+0200 maurycy@gimel /Users/maurycy/work/cpython (main 9bd670c?) % ./python.exe
Python 3.16.0a0 (heads/main:9bd670cba21, Sep 10 2026, 17:37:43) [Clang 21.0.0 (clang-2100.1.1.101)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import _testcapi
... import sys
...
... ba = bytearray(b"abc123456")
... ba += b"7"
...
... try:
... _testcapi.set_nomemory(0)
... del ba[::2]
... except MemoryError: pass
... finally: _testcapi.remove_mem_hooks()
...
... ba
...
bytearray(b'')Unfortunately, I don't have bandwitch to investigate what's the exact fix here |
alloc is just the ob_bytes_object size.
|
The PR became quite big, so I modified it to focus strictly on |
|
Ok, I merged the first part. I created the follow-up PR for bytearray: PR gh-157340. |
|
Ah, Py_TRACE_REFS builds are special, they cannot test set_nomemory(). I wrote PR gh-157350 to fix test_capi on Py_TRACE_REFS build. |
…7243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
…7243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
|
Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14. |
|
Thanks @vstinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.15. |
|
Sorry, @vstinner, I could not cleanly backport this to Please backport manually with cherry_picker, see the devguide for more information. |
|
Sorry, @vstinner, I could not cleanly backport this to Please backport manually with cherry_picker, see the devguide for more information. |
…7243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Add BaseWriterTest for BytesWriterTest and ByteArrayWriterTest. Add _testcapi.PyBytesWriter_small_buffer constant. Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com> (cherry picked from commit f5dd52d)
|
GH-159260 is a backport of this pull request to the 3.15 branch. |
…#159260) gh-157242: Fix PyBytesWriter_Resize() on MemoryError (#157243) If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error. Add BaseWriterTest for BytesWriterTest and ByteArrayWriterTest. Add _testcapi.PyBytesWriter_small_buffer constant. (cherry picked from commit f5dd52d) Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
If PyBytesWriter_Resize() fails, leave the writer unchanged.
Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error.