comparison 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
comparison
equal deleted inserted replaced
45094:b4c35e439ea5 45095:8e04607023e5
78 if isinstance(stream, LineBufferedWrapper): 78 if isinstance(stream, LineBufferedWrapper):
79 return stream 79 return stream
80 return LineBufferedWrapper(stream) 80 return LineBufferedWrapper(stream)
81 81
82 82
83 class WriteAllWrapper(object):
84 def __init__(self, orig):
85 self.orig = orig
86
87 def __getattr__(self, attr):
88 return getattr(self.orig, attr)
89
90 def write(self, s):
91 write1 = self.orig.write
92 m = memoryview(s)
93 total_to_write = len(s)
94 total_written = 0
95 while total_written < total_to_write:
96 total_written += write1(m[total_written:])
97 return total_written
98
99
100 io.IOBase.register(WriteAllWrapper)
101
102
103 def make_write_all(stream):
104 assert pycompat.ispy3
105 if isinstance(stream, WriteAllWrapper):
106 return stream
107 if isinstance(stream, io.BufferedIOBase):
108 # The io.BufferedIOBase.write() contract guarantees that all data is
109 # written.
110 return stream
111 # In general, the write() method of streams is free to write only part of
112 # the data.
113 return WriteAllWrapper(stream)
114
115
83 if pycompat.ispy3: 116 if pycompat.ispy3:
84 # Python 3 implements its own I/O streams. 117 # Python 3 implements its own I/O streams.
85 # TODO: .buffer might not exist if std streams were replaced; we'll need 118 # TODO: .buffer might not exist if std streams were replaced; we'll need
86 # a silly wrapper to make a bytes stream backed by a unicode one. 119 # a silly wrapper to make a bytes stream backed by a unicode one.
87 stdin = sys.stdin.buffer 120 stdin = sys.stdin.buffer
88 stdout = sys.stdout.buffer 121 stdout = make_write_all(sys.stdout.buffer)
89 if isatty(stdout): 122 if isatty(stdout):
90 # The standard library doesn't offer line-buffered binary streams. 123 # The standard library doesn't offer line-buffered binary streams.
91 stdout = make_line_buffered(stdout) 124 stdout = make_line_buffered(stdout)
92 stderr = sys.stderr.buffer 125 stderr = make_write_all(sys.stderr.buffer)
93 else: 126 else:
94 # Python 2 uses the I/O streams provided by the C library. 127 # Python 2 uses the I/O streams provided by the C library.
95 stdin = sys.stdin 128 stdin = sys.stdin
96 stdout = sys.stdout 129 stdout = sys.stdout
97 if isatty(stdout): 130 if isatty(stdout):