@@ -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,36 @@ 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 = 'Timeout: the command "%s" did not complete in %d secs.' % (
1477+ " " .join (redacted_command ),
1478+ timeout ,
1479+ )
1480+ return err if universal_newlines else err .encode (defenc )
1481+
14701482 def communicate () -> Tuple [AnyStr , AnyStr ]:
1483+ assert watchdog is not None
1484+ assert kill_check is not None
14711485 watchdog .start ()
14721486 out , err = proc .communicate ()
14731487 watchdog .cancel ()
14741488 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 )
1489+ err = make_timeout_error ()
14811490 return out , err
14821491
14831492 # END helpers
14841493
14851494 kill_check = threading .Event ()
14861495 watchdog = threading .Timer (timeout , kill_process , args = (proc .pid ,))
1496+ timeout_error = make_timeout_error
14871497 else :
14881498 communicate = proc .communicate
14891499
@@ -1504,15 +1514,24 @@ def communicate() -> Tuple[AnyStr, AnyStr]:
15041514 status = proc .returncode
15051515 else :
15061516 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 ()
1517+ if watchdog is not None :
1518+ watchdog .start ()
1519+ try :
1520+ if proc .stdout is not None :
1521+ stream_copy (proc .stdout , output_stream , max_chunk_size )
1522+ stdout_value = proc .stdout .read ()
1523+ if proc .stderr is not None :
1524+ stderr_value = proc .stderr .read ()
1525+ status = proc .wait ()
1526+ finally :
1527+ if watchdog is not None :
1528+ watchdog .cancel ()
15121529 # Strip trailing "\n".
15131530 if stderr_value is not None and stderr_value .endswith (newline ): # type: ignore[arg-type]
15141531 stderr_value = stderr_value [:- 1 ]
1515- status = proc .wait ()
1532+ if kill_check is not None and kill_check .is_set ():
1533+ assert timeout_error is not None
1534+ stderr_value = timeout_error ()
15161535 # END stdout handling
15171536 finally :
15181537 if proc .stdout is not None :
0 commit comments