Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asyncio proc.kill() and proc.wait() are counter intuitive #119710

Open
piwicode opened this issue May 29, 2024 · 5 comments
Open

asyncio proc.kill() and proc.wait() are counter intuitive #119710

piwicode opened this issue May 29, 2024 · 5 comments
Labels
topic-asyncio type-bug An unexpected behavior, bug, or error

Comments

@piwicode
Copy link

piwicode commented May 29, 2024

Bug report

Bug description:

On Linux proc.wait() does not return after proc.kill() when there as sub/sub processes.
This only happens when the standard streams are piped, it looks like wait() blocks until the pipes are closed.

import asyncio
import os
import time
async def main():
    proc = await asyncio.create_subprocess_exec(
        "/usr/bin/python3", "-c", "import os;os.system('sleep 20')",
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)
    
    time.sleep(1)
    os.system("ps -f --forest --format pid,ppid,pgid,sid,comm")

    print(f">> This python proces pid={os.getpid()} has a `python3` child process, which has a `sleep` child process.")
    print(f">> Kill the `python3` child process pid={proc.pid}")
    proc.kill()

    os.system("ps -f --forest --format pid,ppid,pgid,sid,comm")
    print(f">> The `python3` child process was killed, the `sleep` process gets orfaned.")
    print(f">> Calling `proc.wait()` or `proc.communicate()` does not return until `sleep` process exits.")
    
    await proc.wait()

asyncio.run(main())

I don't know if it's a documentation issue or a bug.

  • It would be more intuitive that proc.wait() after a proc.kill() returned when the process exited, and does not depend on other processes completion.
  • It would be more intuitive to use have the same behavior when standard streams are piped and when they are not.

CPython versions tested on:

3.11

Operating systems tested on:

Linux

@piwicode piwicode added the type-bug An unexpected behavior, bug, or error label May 29, 2024
@github-project-automation github-project-automation bot moved this to Todo in asyncio May 29, 2024
@vstinner
Copy link
Member

vstinner commented Jun 3, 2024

It's a surprising feature. communicate() waits until stdout (and stderr) is closed. If a child process spawns "sleep 20", the "sleep 20" inherits stdout and keeps it open for 20 seconds.

Maybe you should create a new process group and kill the whole process group, rather than only killing the "parent" process (parent of "sleep 20"). See the start_new_session parameter of subprocess.Popen and then use os.killpg().

That's what I did in test.libregrtest to make sure that CTRL+C kills immediately all processes, and not only direct child processes.

@piwicode
Copy link
Author

piwicode commented Jun 3, 2024 via email

@vstinner
Copy link
Member

vstinner commented Jun 3, 2024

wait() and communicate() are very different.

@piwicode
Copy link
Author

piwicode commented Jun 4, 2024

Indeed. This issue is about proc.wait() documentation. I updated the code snippet to male it clearer.

@benzea
Copy link

benzea commented Oct 20, 2024

I really consider this a pretty bad bug. It is absolutely impossible to wait for the executed process. I specifically wanted to wait for the launched process and not the pipe to be closed. Unfortunately, with the current API that is pretty much impossible.

There is not even a timeout= parameter for wait(), so I am now resorting to having a second task that specifically closes the pipe using proc.stdout._transport._protocol.pipe.close() (after killing the process just in case). I consider this a really nasty workaround, as I just need to wait for the process to quit. However, adding a second child watch is also not really possible …

Considering we have the mess, maybe one could:

  1. Properly document the wait() behaviour
  2. Fix Process.wait() to not immediately return if the process already quit (BaseSubprocessTransport._wait needs to test self._finished not self._returncode to be consistent)
  3. Add a Process.waitpid() function with the expected behaviour that does not wait for the pipes to be closed
  4. Maybe add a close() function that can be used to explicitly close all pipes. Though I suppose that will happen anyway when the object is deleted.

hazyfossa added a commit to hazyfossa/pyoci that referenced this issue Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-asyncio type-bug An unexpected behavior, bug, or error
Projects
Status: Todo
Development

No branches or pull requests

4 participants