Make the new cmd_showing_output select and display data from stderr in
[python_utils.git] / exec_utils.py
index b52f52f0dc785033fc437a638c734d79cf8d5aa8..edbd21f5497b43fef739c4e1998cd62cdb695ea5 100644 (file)
@@ -2,8 +2,10 @@
 
 import atexit
 import logging
+import selectors
 import shlex
 import subprocess
+import sys
 from typing import List, Optional
 
 
@@ -11,17 +13,32 @@ logger = logging.getLogger(__file__)
 
 
 def cmd_showing_output(command: str) -> None:
+    line_enders = set([b'\n', b'\r'])
     p = subprocess.Popen(
         command,
         shell=True,
         bufsize=0,
         stdout=subprocess.PIPE,
         stderr=subprocess.PIPE,
+        universal_newlines=False,
     )
-    for line in iter(p.stdout.readline, b''):
-        print(line.decode('utf-8'), end='')
-    p.stdout.close()
-    p.wait()
+    sel = selectors.DefaultSelector()
+    sel.register(p.stdout, selectors.EVENT_READ)
+    sel.register(p.stderr, selectors.EVENT_READ)
+    while True:
+        for key, _ in sel.select():
+            char = key.fileobj.read(1)
+            if not char:
+                p.wait()
+                return
+            if key.fileobj is p.stdout:
+                sys.stdout.buffer.write(char)
+                if char in line_enders:
+                    sys.stdout.flush()
+            else:
+                sys.stderr.buffer.write(char)
+                if char in line_enders:
+                    sys.stderr.flush()
 
 
 def cmd_with_timeout(command: str, timeout_seconds: Optional[float]) -> int: