New submission from STINNER Victor <vstin...@python.org>:

os.wait() and os.waitpid() returns a "status" number which is not easy to 
return. It's made of two information: (how the process completed, value).

The usual way to handle it is to use a code which looks like:

    if os.WIFSIGNALED(status):
        self.returncode = -os.WTERMSIG(status)
    elif os.WIFEXITED(status):
        self.returncode = os.WEXITSTATUS(status)
    elif os.WIFSTOPPED(status):
        self.returncode = -os.WSTOPSIG(status)
    else:
        raise Exception("... put your favorite error message here ...")

It's not convenient to have to duplicate this code each time we have to handle 
a wait status.

Moreover, WIFSTOPPED() is commonly treated as "the process was killed by a 
signal", whereas the process is still alive but was only stopped. WIFSTOPPED() 
should only happen when the process is traced (by ptrace), or if waitpid() was 
called with WUNTRACED option.

The common case is not to trace a process or to use WUNTRACED. Moreover, if 
WIFSTOPPED() is true, the process is still alive and can continue its 
execution. It's bad to consider it as completed.


The subprocess module has such bug: Popen._handle_exitstatus() returns 
-os.WSTOPSIG(sts) if os.WIFSTOPPED(sts) is true.


On the other side, the pure Python implementation os._spawnvef() calls again 
waitpid() if WIFSTOPPED() is true. That sounds like a better behavior.

    while 1:
        wpid, sts = waitpid(pid, 0)
        if WIFSTOPPED(sts):
            continue

        elif WIFSIGNALED(sts):
            return -WTERMSIG(sts)
        elif WIFEXITED(sts):
            return WEXITSTATUS(sts)
        else:
            raise OSError("Not stopped, signaled or exited???")

But I'm not sure how WIFSTOPPED() can be true, since this function creates a 
child process using os.fork() and it doesn't use os.WUNTRACED flag.


I propose to add a private os._wait_status_to_returncode(status) helper 
function:
---
os._wait_status_to_returncode(status) -> int

Convert a wait() or waitpid() status to a returncode.

If WIFEXITED(status) is true, return WEXITSTATUS(status).
If WIFSIGNALED(status) is true, return -WTERMSIG(status).
Otherwise, raise a ValueError.

If the process is being traced or if waitpid() was called with WUNTRACED
option, the caller must first check if WIFSTOPPED(status) is true.
This function must not be called if WIFSTOPPED(status) is true.
---

I'm not sure if it's a good idea to add the helper as a private function. 
Someone may discover it and starts to use it. If we decide to make it public 
tomorrow, removing os._wait_status_to_returncode() would break code.

Maybe it's better to directly a public function? But I'm not sure if it's 
useful, nor if the function name is good, nor if good to helper an function 
function directly in the os module.

Maybe such helper should be added to shutil instead which is more the 
"high-level" flavor of the os module?

I chose to add it to the os module for different reasons:

* Existing code using os.WEXITSTATUS() and friends usually only uses the os 
module.
* It's convenient to be able to use os._wait_status_to_returncode(status) in 
the subprocess module without adding a dependency (import) on the shutil module.
* os.wait() and os.waitpid() live in the os module: it's convenient to have an 
helper functon in the same module.

What do you think?

* Is it worth it to add os._wait_status_to_returncode() helper function?

* If you like the idea, propose a better name!

* Should it remain private first?

----------
components: Library (Lib)
messages: 365195
nosy: vstinner
priority: normal
severity: normal
status: open
title: Add os._wait_status_to_returncode() helper function
versions: Python 3.9

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue40094>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to