diff options
author | Nadeshiko Manju <me@manjusaka.me> | 2025-05-05 01:15:31 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-05 01:15:31 (GMT) |
commit | 2bbcaedb75942389dacb51866948f40de5951c9c (patch) | |
tree | 653df3a0075990aae5695d7b61addfc47a062062 | |
parent | 51d2459e4d70e9a6551d053b2492f9405a6d9f17 (diff) | |
download | cpython-2bbcaedb75942389dacb51866948f40de5951c9c.zip cpython-2bbcaedb75942389dacb51866948f40de5951c9c.tar.gz cpython-2bbcaedb75942389dacb51866948f40de5951c9c.tar.bz2 |
gh-133089: Use original timeout value for `TimeoutExpired` when the func `subprocess.run` is called with a timeout (GH-133103)
Signed-off-by: Manjusaka <me@manjusaka.me>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
-rw-r--r-- | Doc/library/subprocess.rst | 18 | ||||
-rw-r--r-- | Lib/subprocess.py | 14 | ||||
-rw-r--r-- | Lib/test/test_subprocess.py | 14 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst | 4 |
4 files changed, 46 insertions, 4 deletions
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 05d09e3..028a786 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -1525,6 +1525,24 @@ handling consistency are valid for these functions. Notes ----- +.. _subprocess-timeout-behavior: + +Timeout Behavior +^^^^^^^^^^^^^^^^ + +When using the ``timeout`` parameter in functions like :func:`run`, +:meth:`Popen.wait`, or :meth:`Popen.communicate`, +users should be aware of the following behaviors: + +1. **Process Creation Delay**: The initial process creation itself cannot be interrupted + on many platform APIs. This means that even when specifying a timeout, you are not + guaranteed to see a timeout exception until at least after however long process + creation takes. + +2. **Extremely Small Timeout Values**: Setting very small timeout values (such as a few + milliseconds) may result in almost immediate :exc:`TimeoutExpired` exceptions because + process creation and system scheduling inherently require time. + .. _converting-argument-sequence: Converting an argument sequence to a string on Windows diff --git a/Lib/subprocess.py b/Lib/subprocess.py index da5f572..54c2eb5 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1235,8 +1235,11 @@ class Popen: finally: self._communication_started = True - - sts = self.wait(timeout=self._remaining_time(endtime)) + try: + sts = self.wait(timeout=self._remaining_time(endtime)) + except TimeoutExpired as exc: + exc.timeout = timeout + raise return (stdout, stderr) @@ -2145,8 +2148,11 @@ class Popen: selector.unregister(key.fileobj) key.fileobj.close() self._fileobj2output[key.fileobj].append(data) - - self.wait(timeout=self._remaining_time(endtime)) + try: + self.wait(timeout=self._remaining_time(endtime)) + except TimeoutExpired as exc: + exc.timeout = orig_timeout + raise # All data exchanged. Translate lists into strings. if stdout is not None: diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 3cb755c..d2db8fb 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -162,6 +162,20 @@ class ProcessTestCase(BaseTestCase): [sys.executable, "-c", "while True: pass"], timeout=0.1) + def test_timeout_exception(self): + try: + subprocess.run(['echo', 'hi'], timeout = -1) + except subprocess.TimeoutExpired as e: + self.assertIn("-1 seconds", str(e)) + else: + self.fail("Expected TimeoutExpired exception not raised") + try: + subprocess.run(['echo', 'hi'], timeout = 0) + except subprocess.TimeoutExpired as e: + self.assertIn("0 seconds", str(e)) + else: + self.fail("Expected TimeoutExpired exception not raised") + def test_check_call_zero(self): # check_call() function with zero return code rc = subprocess.check_call(ZERO_RETURN_CMD) diff --git a/Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst b/Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst new file mode 100644 index 0000000..8c4257a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst @@ -0,0 +1,4 @@ +Use original timeout value for :exc:`subprocess.TimeoutExpired` +when the func :meth:`subprocess.run` is called with a timeout +instead of sometimes a confusing partial remaining time out value +used internally on the final ``wait()``. |