Skip to content

With asyncio subprocess, send_signal() and the child process watcher will both call waitpid() #87744

Description

@lincheney
mannequin
BPO 43578
Nosy @asvetlov, @1st1, @lincheney

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2021-03-21.06:53:15.574>
labels = ['type-bug', '3.9', 'expert-asyncio']
title = 'With asyncio subprocess, send_signal() and the child process watcher will both call waitpid()'
updated_at = <Date 2021-03-21.06:53:15.574>
user = 'https://github.com/lincheney'

bugs.python.org fields:

activity = <Date 2021-03-21.06:53:15.574>
actor = 'lincheney'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['asyncio']
creation = <Date 2021-03-21.06:53:15.574>
creator = 'lincheney'
dependencies = []
files = []
hgrepos = []
issue_num = 43578
keywords = []
message_count = 1.0
messages = ['389218']
nosy_count = 3.0
nosy_names = ['asvetlov', 'yselivanov', 'lincheney']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue43578'
versions = ['Python 3.9']

Linked PRs

Activity

  1. lincheney commented on Mar 21, 2021

    lincheneymannequin
    MannequinAuthor

    Under unix, when creating a asyncio subprocess, the child process watcher will call waitpid() to reap the child, but if you call send_signal() (or terminate() or kill() ) on the asyncio subprocess, this will also call waitpid(), causing exactly one of these to fail, as you cannot call waitpid() on a PID more than once.

    If the send_signal() fails, this doesn't seem much of an issue.
    If the child process watcher fails however, it sets the returncode to 255 and also returns 255 when running wait() and also emits a warning.

    I've seen this behaviour with the ThreadedChildWatcher, but possibly other Unix child watchers that use waitpid() suffer from the same problem.

    The behaviour is racey (it depends on which one completes the waitpid() first), but if you try it enough it will appear:

    import asyncio
    import signal
    
    async def main():
        while True:
            proc = await asyncio.create_subprocess_exec('sleep', '0.1')
            await asyncio.sleep(0.1)
            try:
                proc.send_signal(signal.SIGUSR1)
            except ProcessLookupError:
                pass
            assert (await proc.wait() != 255)
    
    asyncio.run(main())
    
    

    The output looks like:

    Unknown child process pid 1394331, will report returncode 255
    Traceback (most recent call last):
      File "/tmp/bob.py", line 14, in <module>
        asyncio.run(main())
      File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
        return future.result()
      File "/tmp/bob.py", line 12, in main
        assert (await proc.wait() != 255)
    AssertionError
    

    This would be expected behaviour if I were explicitly calling waitpid() myself (ie I'm shooting my own foot, so I'd deserve the bad behaviour), but that's not the case here nor any other exotic code.

  2. transferred this issue fromon Apr 10, 2022
  3. FabianElsmer commented on Oct 20, 2022

    @FabianElsmer

    Did you find a workaround for this? I tried the different watcher classes asyncio provides using your example script, but all of them show this error after a while

  4. gvanrossum commented on Oct 20, 2022

    @gvanrossum
    Member

    This one is new to me. I’m not sure how to approach it yet. The only workaround I can think of without digging in the code would be to ignore the error using try/except, but that’s a last resort.

  5. gvanrossum commented on Oct 20, 2022

    @gvanrossum
    Member

    @graingert Any idea here?

  6. gvanrossum commented on Oct 21, 2022

    @gvanrossum
    Member

    Oops, I see the problem isn't an exception so much as a lost exit status. I'm guessing we need to improve the communication between asyncio.subprocess.Process.wait() and the child watchers.

  7. kumaraditya303 commented on Oct 22, 2022

    @kumaraditya303
    Contributor

    On my TODO list but low priority, I can review a PR fixing this though.

  8. 11 remaining items

  9. added a commit that references this issue on Jul 1, 2024
  10. added a commit that references this issue on Jul 1, 2024
  11. moved this from Todo to Done in asyncioon Jul 1, 2024
  12. added a commit that references this issue on Jul 3, 2024
  13. added a commit that references this issue on Jul 11, 2024
  14. added a commit that references this issue on Jul 17, 2024
  15. added a commit that references this issue on Nov 20, 2024
  16. astrand commented on Dec 3, 2024

    @astrand
    Contributor

    This is a major issue for us, I am surprised that this issue is not getting more attention. It can be reproduced with the test case above on Python 3.9 and 3.10; please add corresponding labels. If I understand it correctly, the problem was introduced with https://bugs.python.org/issue38630 . Is it correct that the only workaround is to use os.kill instead of proc.terminate/proc.kill?

  17. gvanrossum commented on Dec 3, 2024

    @gvanrossum
    Member

    This is a major issue for us, I am surprised that this issue is not getting more attention. It can be reproduced with the test case above on Python 3.9 and 3.10; please add corresponding labels. If I understand it correctly, the problem was introduced with https://bugs.python.org/issue38630 . Is it correct that the only workaround is to use os.kill instead of proc.terminate/proc.kill?

    Temper your rhetoric, please. Just upgrade to Python 3.13.

  18. astrand commented on Dec 3, 2024

    @astrand
    Contributor

    This is a major issue for us, I am surprised that this issue is not getting more attention. It can be reproduced with the test case above on Python 3.9 and 3.10; please add corresponding labels. If I understand it correctly, the problem was introduced with https://bugs.python.org/issue38630 . Is it correct that the only workaround is to use os.kill instead of proc.terminate/proc.kill?

    Temper your rhetoric, please. Just upgrade to Python 3.13.

    Easier said than done. In our case the situation is the same as for most users, I believe: Python is provided by the platform. In our case, Yocto Kirkstone, which includes Python 3.10. In theory we could upgrade to the next Yocto LTS release which is Scarthgap, but it only includes Python 3.12; not 3.13.

    The workaround with os.kill seems to work, though, at least with SafeChildWatcher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13only security fixes3.14bugs and security fixestopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions