mirror of
https://github.com/python/cpython.git
synced 2025-07-07 11:25:30 +00:00
gh-133982: Test _pyio.BytesIO in free-threaded tests (gh-136218)
This commit is contained in:
parent
b4991056f4
commit
48cb9b6112
5 changed files with 80 additions and 47 deletions
|
@ -719,6 +719,9 @@ than raw I/O does.
|
|||
The optional argument *initial_bytes* is a :term:`bytes-like object` that
|
||||
contains initial data.
|
||||
|
||||
Methods may be used from multiple threads without external locking in
|
||||
:term:`free threading` builds.
|
||||
|
||||
:class:`BytesIO` provides or overrides these methods in addition to those
|
||||
from :class:`BufferedIOBase` and :class:`IOBase`:
|
||||
|
||||
|
|
80
Lib/_pyio.py
80
Lib/_pyio.py
|
@ -876,16 +876,28 @@ class BytesIO(BufferedIOBase):
|
|||
_buffer = None
|
||||
|
||||
def __init__(self, initial_bytes=None):
|
||||
# Use to keep self._buffer and self._pos consistent.
|
||||
self._lock = Lock()
|
||||
|
||||
buf = bytearray()
|
||||
if initial_bytes is not None:
|
||||
buf += initial_bytes
|
||||
self._buffer = buf
|
||||
self._pos = 0
|
||||
|
||||
with self._lock:
|
||||
self._buffer = buf
|
||||
self._pos = 0
|
||||
|
||||
def __getstate__(self):
|
||||
if self.closed:
|
||||
raise ValueError("__getstate__ on closed file")
|
||||
return self.__dict__.copy()
|
||||
with self._lock:
|
||||
state = self.__dict__.copy()
|
||||
del state['_lock']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.__dict__.update(state)
|
||||
self._lock = Lock()
|
||||
|
||||
def getvalue(self):
|
||||
"""Return the bytes value (contents) of the buffer
|
||||
|
@ -918,14 +930,16 @@ class BytesIO(BufferedIOBase):
|
|||
raise TypeError(f"{size!r} is not an integer")
|
||||
else:
|
||||
size = size_index()
|
||||
if size < 0:
|
||||
size = len(self._buffer)
|
||||
if len(self._buffer) <= self._pos:
|
||||
return b""
|
||||
newpos = min(len(self._buffer), self._pos + size)
|
||||
b = self._buffer[self._pos : newpos]
|
||||
self._pos = newpos
|
||||
return bytes(b)
|
||||
|
||||
with self._lock:
|
||||
if size < 0:
|
||||
size = len(self._buffer)
|
||||
if len(self._buffer) <= self._pos:
|
||||
return b""
|
||||
newpos = min(len(self._buffer), self._pos + size)
|
||||
b = self._buffer[self._pos : newpos]
|
||||
self._pos = newpos
|
||||
return bytes(b)
|
||||
|
||||
def read1(self, size=-1):
|
||||
"""This is the same as read.
|
||||
|
@ -941,12 +955,14 @@ class BytesIO(BufferedIOBase):
|
|||
n = view.nbytes # Size of any bytes-like object
|
||||
if n == 0:
|
||||
return 0
|
||||
pos = self._pos
|
||||
if pos > len(self._buffer):
|
||||
# Pad buffer to pos with null bytes.
|
||||
self._buffer.resize(pos)
|
||||
self._buffer[pos:pos + n] = b
|
||||
self._pos += n
|
||||
|
||||
with self._lock:
|
||||
pos = self._pos
|
||||
if pos > len(self._buffer):
|
||||
# Pad buffer to pos with null bytes.
|
||||
self._buffer.resize(pos)
|
||||
self._buffer[pos:pos + n] = b
|
||||
self._pos += n
|
||||
return n
|
||||
|
||||
def seek(self, pos, whence=0):
|
||||
|
@ -963,9 +979,11 @@ class BytesIO(BufferedIOBase):
|
|||
raise ValueError("negative seek position %r" % (pos,))
|
||||
self._pos = pos
|
||||
elif whence == 1:
|
||||
self._pos = max(0, self._pos + pos)
|
||||
with self._lock:
|
||||
self._pos = max(0, self._pos + pos)
|
||||
elif whence == 2:
|
||||
self._pos = max(0, len(self._buffer) + pos)
|
||||
with self._lock:
|
||||
self._pos = max(0, len(self._buffer) + pos)
|
||||
else:
|
||||
raise ValueError("unsupported whence value")
|
||||
return self._pos
|
||||
|
@ -978,18 +996,20 @@ class BytesIO(BufferedIOBase):
|
|||
def truncate(self, pos=None):
|
||||
if self.closed:
|
||||
raise ValueError("truncate on closed file")
|
||||
if pos is None:
|
||||
pos = self._pos
|
||||
else:
|
||||
try:
|
||||
pos_index = pos.__index__
|
||||
except AttributeError:
|
||||
raise TypeError(f"{pos!r} is not an integer")
|
||||
|
||||
with self._lock:
|
||||
if pos is None:
|
||||
pos = self._pos
|
||||
else:
|
||||
pos = pos_index()
|
||||
if pos < 0:
|
||||
raise ValueError("negative truncate position %r" % (pos,))
|
||||
del self._buffer[pos:]
|
||||
try:
|
||||
pos_index = pos.__index__
|
||||
except AttributeError:
|
||||
raise TypeError(f"{pos!r} is not an integer")
|
||||
else:
|
||||
pos = pos_index()
|
||||
if pos < 0:
|
||||
raise ValueError("negative truncate position %r" % (pos,))
|
||||
del self._buffer[pos:]
|
||||
return pos
|
||||
|
||||
def readable(self):
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import io
|
||||
import _pyio as pyio
|
||||
import threading
|
||||
from unittest import TestCase
|
||||
from test.support import threading_helper
|
||||
from random import randint
|
||||
from io import BytesIO
|
||||
from sys import getsizeof
|
||||
|
||||
|
||||
class TestBytesIO(TestCase):
|
||||
class ThreadSafetyMixin:
|
||||
# Test pretty much everything that can break under free-threading.
|
||||
# Non-deterministic, but at least one of these things will fail if
|
||||
# BytesIO object is not free-thread safe.
|
||||
|
@ -90,20 +91,27 @@ class TestBytesIO(TestCase):
|
|||
barrier.wait()
|
||||
getsizeof(b)
|
||||
|
||||
self.check([write] * 10, BytesIO())
|
||||
self.check([writelines] * 10, BytesIO())
|
||||
self.check([write] * 10 + [truncate] * 10, BytesIO())
|
||||
self.check([truncate] + [read] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([truncate] + [read1] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([truncate] + [readline] * 10, BytesIO(b'0\n'*20480))
|
||||
self.check([truncate] + [readlines] * 10, BytesIO(b'0\n'*20480))
|
||||
self.check([truncate] + [readinto] * 10, BytesIO(b'0\n'*204800), bytearray(b'0\n'*204800))
|
||||
self.check([close] + [write] * 10, BytesIO())
|
||||
self.check([truncate] + [getvalue] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([truncate] + [getbuffer] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([truncate] + [iter] * 10, BytesIO(b'0\n'*20480))
|
||||
self.check([truncate] + [getstate] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([truncate] + [setstate] * 10, BytesIO(b'0\n'*204800), (b'123', 0, None))
|
||||
self.check([truncate] + [sizeof] * 10, BytesIO(b'0\n'*204800))
|
||||
self.check([write] * 10, self.ioclass())
|
||||
self.check([writelines] * 10, self.ioclass())
|
||||
self.check([write] * 10 + [truncate] * 10, self.ioclass())
|
||||
self.check([truncate] + [read] * 10, self.ioclass(b'0\n'*204800))
|
||||
self.check([truncate] + [read1] * 10, self.ioclass(b'0\n'*204800))
|
||||
self.check([truncate] + [readline] * 10, self.ioclass(b'0\n'*20480))
|
||||
self.check([truncate] + [readlines] * 10, self.ioclass(b'0\n'*20480))
|
||||
self.check([truncate] + [readinto] * 10, self.ioclass(b'0\n'*204800), bytearray(b'0\n'*204800))
|
||||
self.check([close] + [write] * 10, self.ioclass())
|
||||
self.check([truncate] + [getvalue] * 10, self.ioclass(b'0\n'*204800))
|
||||
self.check([truncate] + [getbuffer] * 10, self.ioclass(b'0\n'*204800))
|
||||
self.check([truncate] + [iter] * 10, self.ioclass(b'0\n'*20480))
|
||||
self.check([truncate] + [getstate] * 10, self.ioclass(b'0\n'*204800))
|
||||
state = self.ioclass(b'123').__getstate__()
|
||||
self.check([truncate] + [setstate] * 10, self.ioclass(b'0\n'*204800), state)
|
||||
self.check([truncate] + [sizeof] * 10, self.ioclass(b'0\n'*204800))
|
||||
|
||||
# no tests for seek or tell because they don't break anything
|
||||
|
||||
class CBytesIOTest(ThreadSafetyMixin, TestCase):
|
||||
ioclass = io.BytesIO
|
||||
|
||||
class PyBytesIOTest(ThreadSafetyMixin, TestCase):
|
||||
ioclass = pyio.BytesIO
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
# * test_univnewlines - tests universal newline support
|
||||
# * test_largefile - tests operations on a file greater than 2**32 bytes
|
||||
# (only enabled with -ulargefile)
|
||||
# * test_free_threading/test_io - tests thread safety of io objects
|
||||
|
||||
################################################################################
|
||||
# ATTENTION TEST WRITERS!!!
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Update Python implementation of :class:`io.BytesIO` to be thread safe.
|
Loading…
Add table
Add a link
Reference in a new issue