Skip to content

Commit e3f861b

Browse files
mcollinaclaude
authored andcommitted
tools: wait for test processes instead of polling
RunProcess polled the child with an exponential backoff that capped at 100 ms, so every test longer than roughly 300 ms paid 30-75 ms of latency after it had already exited. Across the parallel suite that adds up to about 160 thread-seconds. Block in wait() instead and let a timer thread deliver the kill when the timeout is crossed. Set the shutdown flag from the SIGINT handler so a worker whose child died from the same ctrl-c does not report it as a failure before the main thread aborts the run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UH48w8TCtYmHd4P2UE6HEY Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: #66432 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent 021f7d8 commit e3f861b

1 file changed

Lines changed: 28 additions & 25 deletions

File tree

‎tools/test.py‎

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ def PrintFailureHeader(self, test):
151151
def Run(self, tasks) -> Dict:
152152
self.Starting()
153153
threads = []
154+
# Flag the shutdown from the signal handler itself so that workers whose
155+
# child died from the same ctrl-c do not report it as a failure first.
156+
def on_sigint(signum, frame):
157+
self.shutdown_event.set()
158+
raise KeyboardInterrupt
159+
previous_handler = signal.signal(signal.SIGINT, on_sigint)
154160
# Spawn N-1 threads and then use this thread as the last one.
155161
# That way -j1 avoids threading altogether which is a nice fallback
156162
# in case of threading problems.
@@ -172,6 +178,8 @@ def Run(self, tasks) -> Dict:
172178
self.shutdown_event.set()
173179
# ...and then reraise the exception to bail out
174180
raise
181+
finally:
182+
signal.signal(signal.SIGINT, previous_handler)
175183
self.Done()
176184
return {
177185
'allPassed': not self.failed and not self.shutdown_event.is_set(),
@@ -680,10 +688,6 @@ def KillProcessWithID(pid, signal_to_send=signal.SIGTERM):
680688
os.kill(pid, signal_to_send)
681689

682690

683-
MAX_SLEEP_TIME = 0.1
684-
INITIAL_SLEEP_TIME = 0.0001
685-
SLEEP_TIME_FACTOR = 1.25
686-
687691
SEM_INVALID_VALUE = -1
688692
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
689693

@@ -726,29 +730,28 @@ def RunProcess(context, timeout, args, **rest):
726730
)
727731
if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
728732
Win32SetErrorMode(prev_error_mode)
729-
# Compute the end time - if the process crosses this limit we
730-
# consider it timed out.
731-
if timeout is None: end_time = None
732-
else: end_time = time.time() + timeout
733+
# Block in wait() instead of polling: a timer thread delivers the kill if
734+
# the process crosses the timeout, and wait() then returns the exit code.
733735
timed_out = False
734-
# Repeatedly check the exit code from the process in a
735-
# loop and keep track of whether or not it times out.
736-
exit_code = None
737-
sleep_time = INITIAL_SLEEP_TIME
738-
739-
while exit_code is None:
740-
if (not end_time is None) and (time.time() >= end_time):
741-
# Kill the process and wait for it to exit.
742-
KillTimedOutProcess(context, process.pid)
743-
exit_code = process.wait()
736+
if timeout is None:
737+
exit_code = process.wait()
738+
else:
739+
def on_timeout():
740+
nonlocal timed_out
741+
if process.returncode is not None:
742+
return
744743
timed_out = True
745-
else:
746-
exit_code = process.poll()
747-
if exit_code is None:
748-
time.sleep(sleep_time)
749-
sleep_time = sleep_time * SLEEP_TIME_FACTOR
750-
if sleep_time > MAX_SLEEP_TIME:
751-
sleep_time = MAX_SLEEP_TIME
744+
try:
745+
KillTimedOutProcess(context, process.pid)
746+
except OSError:
747+
pass
748+
timer = threading.Timer(timeout, on_timeout)
749+
timer.daemon = True
750+
timer.start()
751+
try:
752+
exit_code = process.wait()
753+
finally:
754+
timer.cancel()
752755
return (process, exit_code, timed_out)
753756

754757

0 commit comments

Comments
 (0)