Author: Amaury Forgeot d'Arc <amaur...@gmail.com> Branch: stdlib-2.7.3 Changeset: r55674:97bc9ee23af4 Date: 2012-06-14 23:13 +0200 http://bitbucket.org/pypy/pypy/changeset/97bc9ee23af4/
Log: CPython Issue #13322: Fix BufferedWriter.write() to ensure that BlockingIOError is raised when the wrapped raw file is non-blocking and the write would block. Previous code assumed that the raw write() would raise BlockingIOError, but RawIOBase.write() is defined to return None when the call would block. diff --git a/pypy/module/_io/interp_bufferedio.py b/pypy/module/_io/interp_bufferedio.py --- a/pypy/module/_io/interp_bufferedio.py +++ b/pypy/module/_io/interp_bufferedio.py @@ -6,6 +6,7 @@ from pypy.interpreter.buffer import RWBuffer from pypy.rlib.rstring import StringBuilder from pypy.rlib.rarithmetic import r_longlong, intmask +from pypy.rlib import rposix from pypy.tool.sourcetools import func_renamer from pypy.module._io.interp_iobase import ( W_IOBase, DEFAULT_BUFFER_SIZE, convert_size, @@ -29,6 +30,16 @@ return False +def make_write_blocking_error(space, written): + w_type = space.gettypeobject(W_BlockingIOError.typedef) + w_value = space.call_function( + w_type, + space.wrap(rposix.get_errno()), + space.wrap("write could not complete without blocking"), + space.wrap(written)) + return OperationError(w_type, w_value) + + class TryLock(object): "A Lock that raises RuntimeError when acquired twice by the same thread" def __init__(self, space): @@ -308,7 +319,7 @@ self._check_init(space) return space.call_method(self.w_raw, "flush") - def _writer_flush_unlocked(self, space, restore_pos=False): + def _writer_flush_unlocked(self, space): if self.write_end == -1 or self.write_pos == self.write_end: return # First, rewind @@ -321,18 +332,8 @@ while self.write_pos < self.write_end: try: n = self._raw_write(space, self.write_pos, self.write_end) - except OperationError, e: - if not e.match(space, space.gettypeobject( - W_BlockingIOError.typedef)): - raise - w_exc = e.get_w_value(space) - assert isinstance(w_exc, W_BlockingIOError) - self.write_pos += w_exc.written - self.raw_pos = self.write_pos - written += w_exc.written - # re-raise the error - w_exc.written = written - raise + except BlockingIOError: + raise make_write_blocking_error(space, 0) self.write_pos += n self.raw_pos = self.write_pos written += n @@ -341,12 +342,6 @@ # blocking another time, possibly indefinitely. space.getexecutioncontext().checksignals() - if restore_pos: - forward = rewind - written - if forward: - self._raw_seek(space, forward, 1) - self.raw_pos += forward - self._writer_reset_buf() def _write(self, space, data): @@ -360,7 +355,11 @@ raise else: break - + + if space.is_w(w_written, space.w_None): + # Non-blocking stream would have blocked. + raise BlockingIOError() + written = space.getindex_w(w_written, space.w_IOError) if not 0 <= written <= len(data): raise OperationError(space.w_IOError, space.wrap( @@ -431,7 +430,7 @@ self._check_init(space) with self.lock: if self.writable: - self._writer_flush_unlocked(space, restore_pos=True) + self._writer_flush_unlocked(space) # Constraints: # 1. we don't want to advance the file position. # 2. we don't want to lose block alignment, so we can't shift the @@ -466,7 +465,7 @@ with self.lock: if self.writable: - self._writer_flush_unlocked(space, restore_pos=True) + self._writer_flush_unlocked(space) # Return up to n bytes. If at least one byte is buffered, we only # return buffered bytes. Otherwise, we do one raw read. @@ -504,7 +503,7 @@ self._reader_reset_buf() # We're going past the buffer's bounds, flush it if self.writable: - self._writer_flush_unlocked(space, restore_pos=True) + self._writer_flush_unlocked(space) while True: # Read until EOF or until read() would block @@ -578,7 +577,7 @@ # XXX potential bug in CPython? The following is not enabled. # We're going past the buffer's bounds, flush it ## if self.writable: - ## self._writer_flush_unlocked(space, restore_pos=True) + ## self._writer_flush_unlocked(space) # Read whole blocks, and don't buffer them while remaining > 0: @@ -698,9 +697,10 @@ for i in range(available): self.buffer[self.write_end + i] = data[i] self.write_end += available - # Raise previous exception - w_exc.written = available - raise + # Modifying the existing exception will will change + # e.characters_written but not e.args[2]. Therefore + # we just replace with a new error. + raise make_write_blocking_error(space, available) # Adjust the raw stream position if it is away from the logical # stream position. This happens if the read buffer has been filled @@ -717,14 +717,8 @@ while remaining > self.buffer_size: try: n = self._write(space, data[written:]) - except OperationError, e: - if not e.match(space, space.gettypeobject( - W_BlockingIOError.typedef)): - raise - w_exc = e.get_w_value(space) - assert isinstance(w_exc, W_BlockingIOError) - written += w_exc.written - remaining -= w_exc.written + except BlockingIOError: + # Write failed because raw file is non-blocking if remaining > self.buffer_size: # Can't buffer everything, still buffer as much as # possible @@ -733,8 +727,8 @@ self.raw_pos = 0 self._adjust_position(self.buffer_size) self.write_end = self.buffer_size - w_exc.written = written + self.buffer_size - raise + written += self.buffer_size + raise make_write_blocking_error(space, written) break written += n remaining -= n diff --git a/pypy/module/_io/interp_fileio.py b/pypy/module/_io/interp_fileio.py --- a/pypy/module/_io/interp_fileio.py +++ b/pypy/module/_io/interp_fileio.py @@ -333,6 +333,8 @@ try: n = os.write(self.fd, data) except OSError, e: + if e.errno == errno.EAGAIN: + return space.w_None raise wrap_oserror(space, e, exception_name='w_IOError') diff --git a/pypy/module/_io/test/test_bufferedio.py b/pypy/module/_io/test/test_bufferedio.py --- a/pypy/module/_io/test/test_bufferedio.py +++ b/pypy/module/_io/test/test_bufferedio.py @@ -336,9 +336,14 @@ except ValueError: pass else: - self._blocker_char = None - self._write_stack.append(b[:n]) - raise _io.BlockingIOError(0, "test blocking", n) + if n > 0: + # write data up to the first blocker + self._write_stack.append(b[:n]) + return n + else: + # cancel blocker and indicate would block + self._blocker_char = None + return None self._write_stack.append(b) return len(b) _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit