Skip to content

subprocess: Popen._internal_poll silently swallows unexpected OSError from waitpid() #153962

Description

@fedonman

On POSIX, subprocess.Popen.poll() can quietly hide a real error.

Internally poll() calls os.waitpid() and catches OSError. It handles two cases: ECHILD (the child was already reaped) and the case where the call comes from __del__. If waitpid() fails with anything else, such as EINVAL, the exception is caught and dropped. returncode stays None, so poll() returns None as if the process were still running and the real failure disappears.

Here is a way to reproduce it. Note that poll() uses a copy of os.waitpid that subprocess saves at import time, so you have to patch it through subprocess._del_safe, not os.waitpid:

import subprocess, errno
from unittest import mock

p = subprocess.Popen(["sleep", "30"])
def fake_waitpid(pid, flags):
    raise OSError(errno.EINVAL, "simulated")
with mock.patch.object(subprocess._del_safe, "waitpid", fake_waitpid):
    print(p.poll(), p.returncode)   # prints: None None
p.kill(); p.wait()

The fix is to re-raise the unexpected errors instead of ignoring them. The __del__ path is handled first (it passes a deadstate value), so it stays unaffected, and the ECHILD handling doesn't change.

Found while going through devdanzin's audit of the standard library, item 8: https://gist.github.com/devdanzin/3198710e3c0128fda5e0a7b4e0768e5f

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-subprocessSubprocess issues.type-bugAn unexpected behavior, bug, or error

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions