33
44"""Benchmark binary execution."""
55
6+ import selectors
67import subprocess
8+ from collections import deque
79from collections .abc import Callable
810from pathlib import Path
911from typing import final
@@ -126,7 +128,8 @@ def run(
126128 if self .verbose :
127129 console .print (f"[dim]$ { ' ' .join (cmd )} [/dim]" )
128130
129- results = []
131+ results : list [str ] = []
132+ diagnostic_lines : deque [str ] = deque (maxlen = 200 )
130133
131134 with Progress (
132135 SpinnerColumn (),
@@ -136,28 +139,51 @@ def run(
136139 ) as progress :
137140 _task = progress .add_task (f"Running { self .backend .value } { benchmark .value } ..." , total = None )
138141
142+ # Merge stderr into stdout so verbose benchmark logs cannot fill a separate pipe and
143+ # block the child process before it emits JSON results.
139144 process = subprocess .Popen (
140145 cmd ,
141146 stdout = subprocess .PIPE ,
142- stderr = subprocess .PIPE ,
147+ stderr = subprocess .STDOUT ,
143148 text = True ,
149+ bufsize = 1 ,
144150 )
145151
146152 assert process .stdout is not None
147- for line in iter (process .stdout .readline , "" ):
148- line = line .strip ()
149- if line :
150- results .append (line )
151- if on_result :
152- on_result (line )
153+ selector = selectors .DefaultSelector ()
154+ selector .register (process .stdout , selectors .EVENT_READ )
155+
156+ try :
157+ while selector .get_map ():
158+ for key , _mask in selector .select (timeout = 0.1 ):
159+ line = key .fileobj .readline ()
160+ if line == "" :
161+ selector .unregister (key .fileobj )
162+ continue
163+
164+ line = line .rstrip ()
165+ if not line :
166+ continue
167+
168+ if line .startswith ("{" ):
169+ results .append (line )
170+ if on_result :
171+ on_result (line )
172+ else :
173+ diagnostic_lines .append (line )
174+ console .print (line , markup = False )
175+ finally :
176+ selector .close ()
153177
154178 ret_code = process .wait ()
155179
156180 if ret_code != 0 :
157- stderr = process .stderr .read () if process .stderr else ""
158181 console .print (f"[red]Benchmark failed with code { process .returncode } [/red]" )
159- if stderr :
160- console .print (f"[red]{ stderr } [/red]" )
161- raise RuntimeError (f"Benchmark { self .backend .value } { benchmark .value } failed: { stderr } " )
182+ diagnostics = "\n " .join (diagnostic_lines )
183+ if diagnostics :
184+ console .print (f"[red]{ diagnostics } [/red]" )
185+ raise RuntimeError (
186+ f"Benchmark { self .backend .value } { benchmark .value } failed: { diagnostics } "
187+ )
162188
163189 return results
0 commit comments