@@ -1435,6 +1435,10 @@ def execute(
14351435 if as_process :
14361436 return self .AutoInterrupt (proc , command )
14371437
1438+ watchdog : Optional [threading .Timer ] = None
1439+ kill_check : Optional [threading .Event ] = None
1440+ timeout_error : Optional [Callable [[], Union [str , bytes ]]] = None
1441+
14381442 if sys .platform != "win32" and kill_after_timeout is not None :
14391443 # Help mypy figure out this is not None even when used inside communicate().
14401444 timeout = kill_after_timeout
@@ -1460,30 +1464,33 @@ def kill_process(pid: int) -> None:
14601464 except OSError :
14611465 pass
14621466 # Tell the main routine that the process was killed.
1467+ assert kill_check is not None
14631468 kill_check .set ()
14641469 except OSError :
14651470 # It is possible that the process gets completed in the duration
14661471 # after timeout happens and before we try to kill the process.
14671472 pass
14681473 return
14691474
1475+ def make_timeout_error () -> Union [str , bytes ]:
1476+ err = f'Timeout: the command "{ " " .join (redacted_command )} " did not complete in { timeout :g} secs.'
1477+ return err if universal_newlines else err .encode (defenc )
1478+
14701479 def communicate () -> Tuple [AnyStr , AnyStr ]:
1480+ assert watchdog is not None
1481+ assert kill_check is not None
14711482 watchdog .start ()
14721483 out , err = proc .communicate ()
14731484 watchdog .cancel ()
14741485 if kill_check .is_set ():
1475- err = 'Timeout: the command "%s" did not complete in %d secs.' % (
1476- " " .join (redacted_command ),
1477- timeout ,
1478- )
1479- if not universal_newlines :
1480- err = err .encode (defenc )
1486+ err = make_timeout_error ()
14811487 return out , err
14821488
14831489 # END helpers
14841490
14851491 kill_check = threading .Event ()
14861492 watchdog = threading .Timer (timeout , kill_process , args = (proc .pid ,))
1493+ timeout_error = make_timeout_error
14871494 else :
14881495 communicate = proc .communicate
14891496
@@ -1504,15 +1511,24 @@ def communicate() -> Tuple[AnyStr, AnyStr]:
15041511 status = proc .returncode
15051512 else :
15061513 max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io .DEFAULT_BUFFER_SIZE
1507- if proc .stdout is not None :
1508- stream_copy (proc .stdout , output_stream , max_chunk_size )
1509- stdout_value = proc .stdout .read ()
1510- if proc .stderr is not None :
1511- stderr_value = proc .stderr .read ()
1514+ if watchdog is not None :
1515+ watchdog .start ()
1516+ try :
1517+ if proc .stdout is not None :
1518+ stream_copy (proc .stdout , output_stream , max_chunk_size )
1519+ stdout_value = proc .stdout .read ()
1520+ if proc .stderr is not None :
1521+ stderr_value = proc .stderr .read ()
1522+ status = proc .wait ()
1523+ finally :
1524+ if watchdog is not None :
1525+ watchdog .cancel ()
15121526 # Strip trailing "\n".
15131527 if stderr_value is not None and stderr_value .endswith (newline ): # type: ignore[arg-type]
15141528 stderr_value = stderr_value [:- 1 ]
1515- status = proc .wait ()
1529+ if kill_check is not None and kill_check .is_set ():
1530+ assert timeout_error is not None
1531+ stderr_value = timeout_error ()
15161532 # END stdout handling
15171533 finally :
15181534 if proc .stdout is not None :
0 commit comments