https://github.com/python/cpython/commit/2bbcaedb75942389dacb51866948f40de5951c9c
commit: 2bbcaedb75942389dacb51866948f40de5951c9c
branch: main
author: Nadeshiko Manju <m...@manjusaka.me>
committer: gpshead <g...@krypto.org>
date: 2025-05-05T01:15:31Z
summary:

gh-133089: Use original timeout value for `TimeoutExpired` when the func 
`subprocess.run` is called with a timeout (GH-133103)

Signed-off-by: Manjusaka <m...@manjusaka.me>
Co-authored-by: Gregory P. Smith <g...@krypto.org>

files:
A Misc/NEWS.d/next/Library/2025-04-29-02-23-04.gh-issue-133089.8Jy1ZS.rst
M Doc/library/subprocess.rst
M Lib/subprocess.py
M Lib/test/test_subprocess.py

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()``.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to