Skip to content

Commit dee916c

Browse files
committed
Add a lock to keep self._buffer and self._pos in sync
This requires updating pickling to exclude the lock
1 parent d889afb commit dee916c

File tree

2 files changed

+55
-39
lines changed

2 files changed

+55
-39
lines changed

Lib/_pyio.py

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -876,16 +876,27 @@ class BytesIO(BufferedIOBase):
876876
_buffer = None
877877

878878
def __init__(self, initial_bytes=None):
879+
# Use to keep self._buffer and self._pos consistent.
880+
self._lock = Lock()
881+
879882
buf = bytearray()
880883
if initial_bytes is not None:
881884
buf += initial_bytes
882-
self._buffer = buf
883-
self._pos = 0
885+
886+
with self._lock:
887+
self._buffer = buf
888+
self._pos = 0
884889

885890
def __getstate__(self):
886891
if self.closed:
887892
raise ValueError("__getstate__ on closed file")
888-
return self.__dict__.copy()
893+
state = self.__dict__.copy()
894+
del state['_lock']
895+
return state
896+
897+
def __setstate__(self, state):
898+
self.__dict__.update(state)
899+
self._lock = Lock()
889900

890901
def getvalue(self):
891902
"""Return the bytes value (contents) of the buffer
@@ -909,23 +920,25 @@ def close(self):
909920
def read(self, size=-1):
910921
if self.closed:
911922
raise ValueError("read from closed file")
912-
if size is None:
913-
size = -1
914-
else:
915-
try:
916-
size_index = size.__index__
917-
except AttributeError:
918-
raise TypeError(f"{size!r} is not an integer")
923+
924+
with self._lock:
925+
if size is None:
926+
size = -1
919927
else:
920-
size = size_index()
921-
if size < 0:
922-
size = len(self._buffer)
923-
if len(self._buffer) <= self._pos:
924-
return b""
925-
newpos = min(len(self._buffer), self._pos + size)
926-
b = self._buffer[self._pos : newpos]
927-
self._pos = newpos
928-
return bytes(b)
928+
try:
929+
size_index = size.__index__
930+
except AttributeError:
931+
raise TypeError(f"{size!r} is not an integer")
932+
else:
933+
size = size_index()
934+
if size < 0:
935+
size = len(self._buffer)
936+
if len(self._buffer) <= self._pos:
937+
return b""
938+
newpos = min(len(self._buffer), self._pos + size)
939+
b = self._buffer[self._pos : newpos]
940+
self._pos = newpos
941+
return bytes(b)
929942

930943
def read1(self, size=-1):
931944
"""This is the same as read.
@@ -941,12 +954,14 @@ def write(self, b):
941954
n = view.nbytes # Size of any bytes-like object
942955
if n == 0:
943956
return 0
944-
pos = self._pos
945-
if pos > len(self._buffer):
946-
# Pad buffer to pos with null bytes.
947-
self._buffer.resize(pos)
948-
self._buffer[pos:pos + n] = b
949-
self._pos += n
957+
958+
with self._lock:
959+
pos = self._pos
960+
if pos > len(self._buffer):
961+
# Pad buffer to pos with null bytes.
962+
self._buffer.resize(pos)
963+
self._buffer[pos:pos + n] = b
964+
self._pos += n
950965
return n
951966

952967
def seek(self, pos, whence=0):
@@ -978,18 +993,20 @@ def tell(self):
978993
def truncate(self, pos=None):
979994
if self.closed:
980995
raise ValueError("truncate on closed file")
981-
if pos is None:
982-
pos = self._pos
983-
else:
984-
try:
985-
pos_index = pos.__index__
986-
except AttributeError:
987-
raise TypeError(f"{pos!r} is not an integer")
996+
997+
with self._lock:
998+
if pos is None:
999+
pos = self._pos
9881000
else:
989-
pos = pos_index()
990-
if pos < 0:
991-
raise ValueError("negative truncate position %r" % (pos,))
992-
del self._buffer[pos:]
1001+
try:
1002+
pos_index = pos.__index__
1003+
except AttributeError:
1004+
raise TypeError(f"{pos!r} is not an integer")
1005+
else:
1006+
pos = pos_index()
1007+
if pos < 0:
1008+
raise ValueError("negative truncate position %r" % (pos,))
1009+
del self._buffer[pos:]
9931010
return pos
9941011

9951012
def readable(self):

Lib/test/test_free_threading/test_io.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,8 @@ def sizeof(barrier, b, *ignore):
104104
self.check([truncate] + [getbuffer] * 10, self.ioclass(b'0\n'*204800))
105105
self.check([truncate] + [iter] * 10, self.ioclass(b'0\n'*20480))
106106
self.check([truncate] + [getstate] * 10, self.ioclass(b'0\n'*204800))
107-
# _pyio uses default __setstate__
108-
if hasattr(self.ioclass, '__setstate__'):
109-
self.check([truncate] + [setstate] * 10, self.ioclass(b'0\n'*204800), (b'123', 0, None))
107+
state = self.ioclass(b'123').__getstate__()
108+
self.check([truncate] + [setstate] * 10, self.ioclass(b'0\n'*204800), state)
110109
self.check([truncate] + [sizeof] * 10, self.ioclass(b'0\n'*204800))
111110

112111
# no tests for seek or tell because they don't break anything

0 commit comments

Comments
 (0)