diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 6fe2ec98fb4088..69f8ee6776e4f5 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -2191,6 +2191,13 @@ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe): # can't get the status. # http://bugs.python.org/issue15756 self.returncode = 0 + else: + # An unexpected error (e.g. EINVAL) must not be + # silently swallowed, leaving returncode as None: + # surface it to the caller. This branch is never + # reached from __del__, which always passes a + # non-None _deadstate. + raise finally: self._waitpid_lock.release() return self.returncode diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index d066ae85dfc51a..4bc97f39b4d3af 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3419,6 +3419,25 @@ def test_leak_fast_process_del_killed(self): else: self.assertNotIn(ident, [id(o) for o in subprocess._active]) + def test_internal_poll_reraises_unexpected_oserror(self): + # An unexpected OSError from waitpid() (i.e. not ECHILD, and not + # during the __del__/_deadstate path) must not be silently swallowed + # by poll(); doing so would leave returncode as None and mask a real + # failure. + p = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(3)"]) + self.addCleanup(p.wait) + self.addCleanup(p.kill) + + def unexpected_waitpid(pid, flags): + raise OSError(errno.EINVAL, "simulated unexpected error") + + with mock.patch.object(subprocess._del_safe, "waitpid", + unexpected_waitpid): + with self.assertRaises(OSError) as cm: + p.poll() + self.assertEqual(cm.exception.errno, errno.EINVAL) + self.assertIsNone(p.returncode) + def test_close_fds_after_preexec(self): fd_status = support.findfile("fd_status.py", subdir="subprocessdata") diff --git a/Misc/NEWS.d/next/Library/2026-07-18-16-35-33.gh-issue-153962.Kp7xQ2.rst b/Misc/NEWS.d/next/Library/2026-07-18-16-35-33.gh-issue-153962.Kp7xQ2.rst new file mode 100644 index 00000000000000..461117fa526b06 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-18-16-35-33.gh-issue-153962.Kp7xQ2.rst @@ -0,0 +1,4 @@ +On POSIX, :meth:`subprocess.Popen.poll` now re-raises an unexpected +:exc:`OSError` from the underlying :func:`os.waitpid` call instead of +silently ignoring it and leaving :attr:`!returncode` set to ``None``. The +special handling of :const:`~errno.ECHILD` is unchanged.