Mercurial > hg-stable
diff mercurial/utils/procutil.py @ 45095:8e04607023e5
procutil: ensure that procutil.std{out,err}.write() writes all bytes
Python 3 offers different kind of streams and it’s not guaranteed for all of
them that calling write() writes all bytes.
When Python is started in unbuffered mode, sys.std{out,err}.buffer are
instances of io.FileIO, whose write() can write less bytes for
platform-specific reasons (e.g. Linux has a 0x7ffff000 bytes maximum and could
write less if interrupted by a signal; when writing to Windows consoles, it’s
limited to 32767 bytes to avoid the "not enough space" error). This can lead to
silent loss of data, both when using sys.std{out,err}.buffer (which may in fact
not be a buffered stream) and when using the text streams sys.std{out,err}
(I’ve created a CPython bug report for that:
https://bugs.python.org/issue41221).
Python may fix the problem at some point. For now, we implement our own wrapper
for procutil.std{out,err} that calls the raw stream’s write() method until all
bytes have been written. We don’t use sys.std{out,err} for larger writes, so I
think it’s not worth the effort to patch them.
author | Manuel Jacob <me@manueljacob.de> |
---|---|
date | Fri, 10 Jul 2020 12:27:58 +0200 |
parents | b4c35e439ea5 |
children | a5fa2761a6cd |
line wrap: on
line diff
--- a/mercurial/utils/procutil.py Sat Jul 11 07:47:04 2020 +0200 +++ b/mercurial/utils/procutil.py Fri Jul 10 12:27:58 2020 +0200 @@ -80,16 +80,49 @@ return LineBufferedWrapper(stream) +class WriteAllWrapper(object): + def __init__(self, orig): + self.orig = orig + + def __getattr__(self, attr): + return getattr(self.orig, attr) + + def write(self, s): + write1 = self.orig.write + m = memoryview(s) + total_to_write = len(s) + total_written = 0 + while total_written < total_to_write: + total_written += write1(m[total_written:]) + return total_written + + +io.IOBase.register(WriteAllWrapper) + + +def make_write_all(stream): + assert pycompat.ispy3 + if isinstance(stream, WriteAllWrapper): + return stream + if isinstance(stream, io.BufferedIOBase): + # The io.BufferedIOBase.write() contract guarantees that all data is + # written. + return stream + # In general, the write() method of streams is free to write only part of + # the data. + return WriteAllWrapper(stream) + + if pycompat.ispy3: # Python 3 implements its own I/O streams. # TODO: .buffer might not exist if std streams were replaced; we'll need # a silly wrapper to make a bytes stream backed by a unicode one. stdin = sys.stdin.buffer - stdout = sys.stdout.buffer + stdout = make_write_all(sys.stdout.buffer) if isatty(stdout): # The standard library doesn't offer line-buffered binary streams. stdout = make_line_buffered(stdout) - stderr = sys.stderr.buffer + stderr = make_write_all(sys.stderr.buffer) else: # Python 2 uses the I/O streams provided by the C library. stdin = sys.stdin