summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNadeshiko Manju <me@manjusaka.me>2025-05-05 01:15:31 (GMT)
committerGitHub <noreply@github.com>2025-05-05 01:15:31 (GMT)
commit2bbcaedb75942389dacb51866948f40de5951c9c (patch)
tree653df3a0075990aae5695d7b61addfc47a062062
parent51d2459e4d70e9a6551d053b2492f9405a6d9f17 (diff)
downloadcpython-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.rst18
-rw-r--r--Lib/subprocess.py14
-rw-r--r--Lib/test/test_subprocess.py14
-rw-r--r--Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst4
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()``.