Skip to content

Commit 4f2e411

Browse files
Use non-blocking reads to speed up shell output
1 parent d356b91 commit 4f2e411

File tree

1 file changed

+28
-17
lines changed

1 file changed

+28
-17
lines changed

zx.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@
2323
"""
2424
import ast
2525
import code
26+
import contextlib
2627
import inspect
27-
import subprocess
28+
import os
29+
import select
2830
import shlex
31+
import subprocess
2932
import sys
3033
import traceback
31-
from typing import Tuple, Union, IO
34+
from typing import Generator, Tuple, Union, IO
3235

3336

3437
def cli() -> None:
@@ -56,38 +59,46 @@ def cli() -> None:
5659
run_zxpy(filename, module)
5760

5861

59-
def create_shell_process(command: str) -> IO[bytes]:
60-
"""Creates a shell process, returning its stdout to read data from."""
62+
@contextlib.contextmanager
63+
def create_shell_process(command: str) -> Generator[IO[bytes], None, None]:
64+
"""Creates a shell process, yielding its stdout to read data from."""
6165
process = subprocess.Popen(
6266
command,
6367
stdout=subprocess.PIPE,
6468
stderr=subprocess.STDOUT,
6569
shell=True,
6670
)
71+
assert process.stdout is not None
72+
yield process.stdout
73+
6774
process.wait()
75+
process.stdout.close()
6876
if process.returncode != 0:
6977
raise ChildProcessError(process.returncode)
7078

71-
assert process.stdout is not None
72-
return process.stdout
73-
7479

7580
def run_shell(command: str) -> str:
7681
"""This is indirectly run when doing ~'...'"""
77-
stdout = create_shell_process(command)
78-
return stdout.read().decode()
82+
with create_shell_process(command) as stdout:
83+
output = stdout.read().decode()
84+
return output
7985

8086

8187
def run_shell_print(command: str) -> None:
8288
"""Version of `run_shell` that prints out the response instead of returning a string."""
83-
stdout = create_shell_process(command)
84-
while True:
85-
char = stdout.read(1)
86-
if not char:
87-
break
88-
89-
sys.stdout.buffer.write(char)
90-
sys.stdout.flush()
89+
with create_shell_process(command) as stdout:
90+
os.set_blocking(stdout.fileno(), False)
91+
92+
poll = select.poll()
93+
poll.register(stdout, select.POLLIN)
94+
while True:
95+
[(_, code)] = poll.poll()
96+
if code == select.POLLHUP:
97+
break
98+
99+
text = stdout.read()
100+
sys.stdout.buffer.write(text)
101+
sys.stdout.buffer.flush()
91102

92103

93104
def run_shell_alternate(command: str) -> Tuple[str, str, int]:

0 commit comments

Comments
 (0)