diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 05d09e304b32bf..028a7861f36798 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 da5f5729e097e9..54c2eb515b60da 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1235,8 +1235,11 @@ def communicate(self, input=None, timeout=None): 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 @@ def _communicate(self, input, endtime, orig_timeout): 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 3cb755cd56cac8..d2db8fbcb64d52 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -162,6 +162,20 @@ def test_call_timeout(self): [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 00000000000000..8c4257a12a90e5 --- /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()``. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy