Skip to content

Commit b9c384b

Browse files
committed
Address review comment
1 parent 81b0d4f commit b9c384b

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

integration/tests/buffered_writer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,24 @@ def write(self, b):
137137
except ValueError:
138138
pass
139139

140+
# 12. the oversized path calls back into Python once per chunk; a raw whose
141+
# write() reallocates the source object's storage must not leave the write
142+
# loop walking freed memory
143+
ba = bytearray(b"A" * 100)
144+
chunks = []
145+
146+
147+
class MutatingWriter:
148+
def write(self, b):
149+
# reallocates ba's backing storage mid-write
150+
ba[0:1] = b"B" * 200000
151+
chunks.append(1)
152+
return 1
153+
154+
155+
w = _io.BufferedWriter(MutatingWriter(), 8)
156+
assert w.write(ba) == 100
157+
# 100 bytes, one accepted per call, until the 8 byte tail is buffered instead
158+
assert len(chunks) == 92, len(chunks)
159+
140160
print("buffered_writer: ok")

src/runtime/modules/IOModule.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,14 +1498,20 @@ class BufferedWriter
14981498
return PyInteger::create(len);
14991499
}
15001500

1501+
// everything below re-enters Python (flush() and raw_write() both end up calling
1502+
// raw.write()), which can run arbitrary code that reallocates the source object's
1503+
// storage - e.g. a raw whose write() resizes the very bytearray being written. Take a
1504+
// snapshot now so `start` cannot dangle mid-write.
1505+
const std::vector<char> payload{ start, start + len };
1506+
15011507
// the data does not fit: drain our buffer to raw first
15021508
if (auto r = flush(); r.is_err()) { return r; }
15031509

15041510
// write oversized data directly to raw, bypassing the buffer
15051511
size_t written = 0;
15061512
size_t remaining = len;
15071513
while (remaining > m_buffer_size) {
1508-
auto r = raw_write(start + written, remaining);
1514+
auto r = raw_write(payload.data() + written, remaining);
15091515
if (r.is_err()) { return r; }
15101516
if (r.unwrap() == py_none()) {
15111517
// TODO: raise BlockingIOError
@@ -1535,7 +1541,7 @@ class BufferedWriter
15351541

15361542
// buffer the tail, which is now guaranteed to fit
15371543
if (remaining > 0) {
1538-
const auto count = this->buffer->sputn(start + written, remaining);
1544+
const auto count = this->buffer->sputn(payload.data() + written, remaining);
15391545
ASSERT(static_cast<size_t>(count) == remaining);
15401546
m_buffered_bytes += remaining;
15411547
written += remaining;

0 commit comments

Comments
 (0)