Skip to content

Commit 00d23bb

Browse files
committed
pythongh-157242: Do not close io.BytesIO on MemoryError
Replace _PyBytes_Resize() with _PyBytes_ResizeKeepOnError(). Fix truncate(): only set string_size on resize success.
1 parent f5dd52d commit 00d23bb

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

Lib/test/test_io/test_memoryio.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import unittest
77
from test import support
8+
from test.support import import_helper
89

910
import gc
1011
import io
@@ -753,6 +754,35 @@ def __buffer__(self, flags):
753754
self.assertEqual(memio.getvalue(), b"01AAA56789")
754755
self.assertEqual(memio.tell(), 5)
755756

757+
def test_memory_error(self):
758+
# gh-157242: io.BytesIO() must not close the file on MemoryError
759+
_testcapi = import_helper.import_module('_testcapi')
760+
761+
# write()
762+
stream = self.ioclass()
763+
stream.write(self.buftype('abc'))
764+
with self.assertRaises(MemoryError):
765+
try:
766+
data = self.buftype('def')
767+
_testcapi.set_nomemory(0)
768+
stream.write(data)
769+
finally:
770+
_testcapi.remove_mem_hooks()
771+
stream.write(self.buftype('123'))
772+
self.assertEqual(stream.getvalue(), self.buftype('abc123'))
773+
774+
# truncate()
775+
data = self.buftype('x' * 100)
776+
stream = self.ioclass()
777+
stream.write(data)
778+
with self.assertRaises(MemoryError):
779+
try:
780+
_testcapi.set_nomemory(0)
781+
stream.truncate(5)
782+
finally:
783+
_testcapi.remove_mem_hooks()
784+
self.assertEqual(stream.getvalue(), data)
785+
756786

757787
class TextIOTestMixin:
758788

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`io.BytesIO` is no longer closed on ``write()`` and ``truncate()``
2+
failure (:exc:`MemoryError`). Patch by Victor Stinner.

Modules/_io/bytesio.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Python.h"
2+
#include "pycore_bytesobject.h" // _PyBytes_ResizeKeepOnError()
23
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
34
#include "pycore_object.h"
45
#include "pycore_pyatomic_ft_wrappers.h"
@@ -108,7 +109,7 @@ resize_unshared_buffer_lock_held(bytesio *self, Py_ssize_t size)
108109
Callers must detach first. */
109110
assert(!self->buf_shared);
110111
#endif
111-
int ret = _PyBytes_Resize(&self->buf, size);
112+
int ret = _PyBytes_ResizeKeepOnError(&self->buf, size);
112113
if (ret == 0) {
113114
clear_shared_buf(self);
114115
}
@@ -758,9 +759,10 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
758759
}
759760

760761
if (new_size < self->string_size) {
761-
self->string_size = new_size;
762-
if (resize_buffer_lock_held(self, new_size) < 0)
762+
if (resize_buffer_lock_held(self, new_size) < 0) {
763763
return NULL;
764+
}
765+
self->string_size = new_size;
764766
}
765767

766768
return PyLong_FromSsize_t(new_size);

0 commit comments

Comments
 (0)