Patchwork [6,of,6,v2] procutil: make mercurial.utils.procutil.stderr unbuffered

login
register
mail settings
Submitter Manuel Jacob
Date July 6, 2020, 12:03 p.m.
Message ID <2763cf96599d07580f47.1594036998@tmp>
Download mbox | patch
Permalink /patch/46635/
State Accepted
Headers show

Comments

Manuel Jacob - July 6, 2020, 12:03 p.m.
# HG changeset patch
# User Manuel Jacob <me@manueljacob.de>
# Date 1593947362 -7200
#      Sun Jul 05 13:09:22 2020 +0200
# Node ID 2763cf96599d07580f47fbafaacd5a8860551a02
# Parent  6109510391c2d89e9c507e9805fb01da9cf514c1
# EXP-Topic stdio
procutil: make mercurial.utils.procutil.stderr unbuffered

For most Mercurial code, it doesn’t make a difference, as the ui object flushes
stderr explicitly (after the change, we could get rid of the explicit flush).
One example where it makes a observable difference is mercurial.util.timed().
Without the patch, the time is not immediately shown on Python 3. With the
patch, it’s shown immediately on all Python versions and platforms.

Patch

diff --git a/mercurial/utils/procutil.py b/mercurial/utils/procutil.py
--- a/mercurial/utils/procutil.py
+++ b/mercurial/utils/procutil.py
@@ -99,6 +99,18 @@ 
     else:
         stdout = os.fdopen(stdout.fileno(), 'wb', 1)
 
+# stderr should be unbuffered
+if pycompat.ispy3:
+    # On Python 3, buffered streams may expose an underlying raw stream. This is
+    # definitively the case for the streams initialized by the interpreter. If
+    # the attribute isn't present, the stream is already unbuffered or doesn't
+    # expose an underlying raw stream, in which case we use the stream as-is.
+    stderr = getattr(stderr, 'raw', stderr)
+elif pycompat.iswindows:
+    # On Windows, stderr is buffered at least when connected to a pipe.
+    stderr = os.fdopen(stderr.fileno(), 'wb', 0)
+# On other platforms, stderr is always unbuffered.
+
 
 findexe = platform.findexe
 _gethgcmd = platform.gethgcmd
diff --git a/tests/test-stdio.py b/tests/test-stdio.py
--- a/tests/test-stdio.py
+++ b/tests/test-stdio.py
@@ -100,6 +100,18 @@ 
             test_stdout_ptys_unbuffered
         )
 
+    def test_stderr_pipes(self):
+        self._test('stderr', _pipes, UNBUFFERED)
+
+    def test_stderr_ptys(self):
+        self._test('stderr', _ptys, UNBUFFERED)
+
+    def test_stderr_pipes_unbuffered(self):
+        self._test('stderr', _pipes, UNBUFFERED, python_args=['-u'])
+
+    def test_stderr_ptys_unbuffered(self):
+        self._test('stderr', _ptys, UNBUFFERED, python_args=['-u'])
+
 
 if __name__ == '__main__':
     import silenttestrunner