_CAN_USE_PIDFD_OPEN = not _mswindows and _can_use_pidfd_open()
_CAN_USE_KQUEUE = not _mswindows and _can_use_kqueue()
+# Maximum timeout passed to poll() / kqueue.control() by Popen._wait():
+# very large values (e.g. timeout=float('inf')) overflow the C timestamp
+# conversion (gh-154836). Longer waits are performed in bounded slices
+# until the deadline, like asyncio's MAXIMUM_SELECT_TIMEOUT.
+_MAXIMUM_WAIT_TIMEOUT = 24 * 3600
+
# These are primarily fail-safe knobs for negatives. A True value does not
# guarantee the given libc/syscall API will be used.
try:
poller = select.poll()
poller.register(pidfd, select.POLLIN)
- events = poller.poll(timeout * 1000)
- if not events:
- raise TimeoutExpired(self.args, timeout)
- return True
+ endtime = _time() + timeout
+ while True:
+ # Clamp very large timeouts (e.g. float('inf')):
+ # they overflow the C timestamp conversion in
+ # poll(). Wait in bounded slices until the real
+ # deadline (gh-154836).
+ delay = min(_deadline_remaining(endtime),
+ _MAXIMUM_WAIT_TIMEOUT)
+ events = poller.poll(max(delay, 0) * 1000)
+ if events:
+ return True
+ if _deadline_remaining(endtime) <= 0:
+ raise TimeoutExpired(self.args, timeout)
finally:
os.close(pidfd)
flags=select.KQ_EV_ADD | select.KQ_EV_ONESHOT,
fflags=select.KQ_NOTE_EXIT,
)
- try:
- events = kq.control([kev], 1, timeout) # wait
- except OSError:
- return False
- else:
- if not events:
+ changelist = [kev]
+ endtime = _time() + timeout
+ while True:
+ # Clamp very large timeouts (e.g. float('inf')):
+ # they overflow the C timestamp conversion in
+ # kqueue.control(). Wait in bounded slices until
+ # the real deadline (gh-154836).
+ delay = min(_deadline_remaining(endtime),
+ _MAXIMUM_WAIT_TIMEOUT)
+ try:
+ events = kq.control(changelist, 1, max(delay, 0))
+ except OSError:
+ return False
+ if events:
+ return True
+ if _deadline_remaining(endtime) <= 0:
raise TimeoutExpired(self.args, timeout)
- return True
+ # The kevent was registered by the first control()
+ # call; don't re-add it on later slices.
+ changelist = None
finally:
kq.close()
self.assertTrue(kq.closed)
self.assertRaises(ValueError, kq.fileno)
+ def test_control_overflowing_timeout(self):
+ # gh-154836: out-of-range timeouts must raise OverflowError,
+ # not a (misleading) TypeError, like select(), poll() and
+ # epoll() do.
+ kq = select.kqueue()
+ self.addCleanup(kq.close)
+ for timeout in (1e300, float('inf'), 2**200):
+ with self.subTest(timeout=timeout):
+ with self.assertRaises(OverflowError):
+ kq.control(None, 0, timeout)
+ # Non-numbers still raise TypeError.
+ with self.assertRaises(TypeError):
+ kq.control(None, 0, "0.1")
+
def test_create_event(self):
from operator import lt, le, gt, ge
self.assertEqual(p.wait(timeout=support.LONG_TIMEOUT), 0)
self.assertFalse(m.called)
+ @unittest.skipIf(mswindows, "requires the POSIX wait implementation")
+ def test_wait_huge_timeout(self):
+ # gh-154836: very large timeout values used to overflow the C
+ # timestamp conversion in poll() / kqueue.control() and raise
+ # OverflowError / TypeError.
+ for timeout in (10**10, sys.maxsize, float('inf')):
+ with self.subTest(timeout=timeout):
+ p = subprocess.Popen(ZERO_RETURN_CMD)
+ self.assertEqual(p.wait(timeout=timeout), 0)
+
+ @unittest.skipIf(mswindows, "requires the POSIX wait implementation")
+ def test_run_huge_timeout(self):
+ # gh-154836: same as test_wait_huge_timeout, via the
+ # subprocess.run() / communicate() code path.
+ cp = subprocess.run(ZERO_RETURN_CMD, timeout=1e10)
+ self.assertEqual(cp.returncode, 0)
+
+ @unittest.skipIf(mswindows, "requires the POSIX wait implementation")
+ def test_wait_slices_do_not_expire_early(self):
+ # A clamped wait slice must not raise TimeoutExpired before the
+ # real deadline: with a tiny slice limit, a process that
+ # outlives many slices must still be waited for successfully.
+ with mock.patch.object(subprocess, "_MAXIMUM_WAIT_TIMEOUT", 0.01):
+ p = subprocess.Popen(self.COMMAND) # sleeps 0.3s
+ self.assertEqual(p.wait(timeout=support.SHORT_TIMEOUT), 0)
+
if __name__ == "__main__":
unittest.main()
--- /dev/null
+Fix :meth:`subprocess.Popen.wait` raising :exc:`TypeError` (macOS and other
+BSDs) or :exc:`OverflowError` (Linux) for very large *timeout* values such
+as ``float('inf')``, a 3.15 regression in the new event-driven wait. Also
+fix :meth:`select.kqueue.control` masking :exc:`OverflowError` for
+out-of-range timeouts as :exc:`TypeError`.
else {
if (_PyTime_FromSecondsObject(&timeout,
otimeout, _PyTime_ROUND_TIMEOUT) < 0) {
- PyErr_Format(PyExc_TypeError,
- "timeout must be a real number or None, not %T",
- otimeout);
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ PyErr_Format(PyExc_TypeError,
+ "timeout must be a real number or None, not %T",
+ otimeout);
+ }
return NULL;
}