GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Cannot cleanly kill a subprocess using high-level asyncio APIs · Issue #88050 · python/cpython

Cannot cleanly kill a subprocess using high-level asyncio APIs #88050

New issue
Copy link
New issue
Copy link
Closed
Closed
Cannot cleanly kill a subprocess using high-level asyncio APIs#88050
Copy link
Assignees
kumaraditya303
Labels
3.11only security fixesonly security fixes3.12only security fixesonly security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

rabraham
mannequin
opened on Apr 19, 2021
Issue body actions
BPO 43884
Nosy @asvetlov, @1st1, @sweeneyde, @kumaraditya303, @skeggse
PRs
  • bpo-43884: Fix asyncio subprocess kill process cleanly when process is blocked #31611
  • gh-88050: Fix asyncio subprocess kill process cleanly when process is blocked #32073
  • Files
  • kill_subprocess.py: Kill subprocess using the high-level asyncio subprocess APIs
  • 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-04-19.02:47:38.245>
    labels = ['3.11', 'type-bug', 'expert-asyncio']
    title = 'Cannot cleanly kill a subprocess using high-level asyncio APIs'
    updated_at = <Date 2022-03-23.09:08:45.631>
    user = 'https://bugs.python.org/rabraham'

    bugs.python.org fields:

    activity = <Date 2022-03-23.09:08:45.631>
    actor = 'kumaraditya'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['asyncio']
    creation = <Date 2021-04-19.02:47:38.245>
    creator = 'rabraham'
    dependencies = []
    files = ['49965']
    hgrepos = []
    issue_num = 43884
    keywords = ['patch']
    message_count = 6.0
    messages = ['391349', '391350', '391369', '391379', '399937', '415859']
    nosy_count = 6.0
    nosy_names = ['asvetlov', 'yselivanov', 'rabraham', 'Dennis Sweeney', 'kumaraditya', 'skeggse']
    pr_nums = ['31611', '32073']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue43884'
    versions = ['Python 3.11']

    Activity

    Next
    1. rabraham commented on Apr 19, 2021

      @rabraham
      rabrahammannequin
      on Apr 19, 2021
      MannequinAuthor
      More actions

      There doesn't appear to be a way to prematurely kill a subprocess using the high-level asyncio subprocess APIs (https://docs.python.org/3.9/library/asyncio-subprocess.html) without getting a traceback on exit.

      On exit, the attached program writes the following to stderr:

      $ python3.9 kill_subprocess.py
      Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x1065f0dc0>
      Traceback (most recent call last):
          ...
          raise RuntimeError('Event loop is closed')
      RuntimeError: Event loop is closed

      If I uncomment # process._transport.close() or comment asyncio.sleep(1), the walkback disappears. (I get the same behavior in python 3.8. I haven't tried other python versions.)

    2. rabraham
      added
      3.8 (EOL)end of lifeend of life
      3.9 (EOL)end of lifeend of life
      topic-asyncio
      type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
      on Apr 19, 2021
    3. rabraham commented on Apr 19, 2021

      @rabraham
      rabrahammannequin
      on Apr 19, 2021
      MannequinAuthor
      More actions

      Reproducing the program here:

          import asyncio
          
          async def test():
              process = await asyncio.create_subprocess_shell(
                  "sleep 2 && echo done",
                  stdout=asyncio.subprocess.PIPE,
              )
              await asyncio.sleep(1)
              process.kill()
              await process.wait()
              # process._transport.close()
          
          asyncio.run(test())

      Can I use the high-level API to kill a subprocess cleanly without having to access the protected member process._transport? Seems like an oversight perhaps?

    4. sweeneyde commented on Apr 19, 2021

      @sweeneyde
      sweeneyde
      on Apr 19, 2021
      Member
      More actions

      Running kill_subprocess.py on Windows 10, I get these results:

      Python 3.7.2 (tags/v3.7.2:9a3ffc0492)
      - raises NotImplementedError in base_events.py, _make_subprocess_transport
      Python 3.8.2 (tags/v3.8.2:7b3ab59)
      - Success
      Python 3.9.0 (tags/v3.9.0:9cf6752)
      - Success
      Python 3.10.0a6 (tags/v3.10.0a6:cc12888)
      - Success

      What is your OS?

    5. rabraham commented on Apr 19, 2021

      @rabraham
      rabrahammannequin
      on Apr 19, 2021
      MannequinAuthor
      More actions

      I see this on MacOS and Linux, but I suspect any Unix-like system would have the same behavior.

      Reacted by byubean
    6. skeggse commented on Aug 19, 2021

      @skeggse
      skeggsemannequin
      on Aug 19, 2021
      Mannequin
      More actions

      I'm also experiencing this, with virtually identical code, on macOS 10.15.7. The given fix (process._transport.close()) also works for me, so I'm just using that workaround for the time being.

    7. kumaraditya303
      added
      3.11only security fixesonly security fixes
      and removed
      3.8 (EOL)end of lifeend of life
      on Feb 28, 2022
    8. 25 remaining items

    9. lysnikolaou
      added a commit that references this issue on Oct 6, 2022

      pythonGH-88050: fix race in closing subprocess pipe in asyncio (pytho…

      25c1004
    10. ambv
      added a commit that references this issue on Oct 6, 2022

      [3.11] GH-88050: fix race in closing subprocess pipe in asyncio (GH-9…

      bd3dcb3
    11. carljm
      added a commit that references this issue on Oct 8, 2022

      Merge branch 'main' into typewatch

      1af2a98
    12. mpage
      added 2 commits that reference this issue on Oct 11, 2022

      pythongh-88050: Fix asyncio subprocess to kill process cleanly when p…

      129a24f

      pythonGH-88050: fix race in closing subprocess pipe in asyncio (pytho…

      bf234f5
    13. theo-brown commented on Feb 2, 2023

      @theo-brown
      theo-brown
      on Feb 2, 2023
      More actions

      Is this meant to be fixed in 3.11+?

    14. gvanrossum commented on Feb 2, 2023

      @gvanrossum
      gvanrossum
      on Feb 2, 2023
      Member
      More actions

      Yes

    15. theo-brown commented on Feb 2, 2023

      @theo-brown
      theo-brown
      on Feb 2, 2023
      More actions

      Which patch? Not in 3.11.0 as far as I can tell.

    16. gvanrossum commented on Feb 2, 2023

      @gvanrossum
      gvanrossum
      on Feb 2, 2023
      Member
      More actions

      #32073 and a followup are present in 3.11.1. They may not be present in 3.11.0.

    17. gertvdijk commented on Apr 2, 2023

      @gertvdijk
      gertvdijk
      on Apr 2, 2023
      More actions

      Hmm. I came here using Google while I'm on 3.11.2 already. This happens sometimes for me on a subprocess wrapped inside a task and stdin/stderr connected. It also only triggers when I am terminating a process myself, instead killing it or letting the loop killing it.

      Found this when hitting random test failures on 3.11.2 and created the minimal reproducer below.

      import asyncio
      import logging
      
      
      async def myfunc_inner() -> None:
          subprocess_task = asyncio.create_task(
              asyncio.create_subprocess_exec(
                  "sleep",
                  "10",
                  # seems to be relevant; unable to trigger without stdout/stderr connected
                  stdout=asyncio.subprocess.PIPE,
                  stderr=asyncio.subprocess.PIPE,
              )
          )
          proc = await subprocess_task
      
          try:
              await asyncio.wait_for(proc.communicate(), timeout=0.02)
          except (asyncio.TimeoutError, asyncio.CancelledError):
              logging.info("Timeout on subprocess command, terminating.")
              # unable to trigger with proc.kill() or without terminating it.
              proc.terminate()
              return
          else:
              logging.info(f"process exited with {proc.returncode}]")
      
      
      # unable to trigger without nested task.
      async def myfunc_outer() -> None:
          await asyncio.wait_for(myfunc_inner(), timeout=1.0)
      
      
      if __name__ == "__main__":
          logging.basicConfig(level=logging.DEBUG)
          loop = asyncio.get_event_loop_policy().new_event_loop()
          loop.run_until_complete(myfunc_outer())
          # This fixes it, but hides the underlying problem I think.
          # loop.run_until_complete(asyncio.sleep(0.01))
          loop.close()

      Running this in a shell loop 200 times, this gives ~ 1-10 failures per loop, looks random.

      $ for i in `seq 1 200`; do python proctimeout.py; done
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x7f5bff42a2a0>
      Traceback (most recent call last):
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_subprocess.py", line 126, in __del__
          self.close()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_subprocess.py", line 104, in close
          proto.pipe.close()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/unix_events.py", line 558, in close
          self._close(None)
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/unix_events.py", line 582, in _close
          self._loop.call_soon(self._call_connection_lost, exc)
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
          self._check_closed()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
          raise RuntimeError('Event loop is closed')
      RuntimeError: Event loop is closed
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      WARNING:asyncio:Loop <_UnixSelectorEventLoop running=False closed=True debug=False> that handles pid 419211 is closed
      Exception ignored in: <function BaseSubprocessTransport.__del__ at 0x7f7dffc162a0>
      Traceback (most recent call last):
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_subprocess.py", line 126, in __del__
          self.close()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_subprocess.py", line 104, in close
          proto.pipe.close()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/unix_events.py", line 558, in close
          self._close(None)
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/unix_events.py", line 582, in _close
          self._loop.call_soon(self._call_connection_lost, exc)
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_events.py", line 761, in call_soon
          self._check_closed()
        File "/home/gert/.pyenv/versions/3.11.2/lib/python3.11/asyncio/base_events.py", line 519, in _check_closed
          raise RuntimeError('Event loop is closed')
      RuntimeError: Event loop is closed
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      DEBUG:asyncio:Using selector: EpollSelector
      Timeout on subprocess command, terminating.
      [...]

      Either removing proc.terminate() or replacing it with proc.kill() makes the traceback disappear. 🤷🏼

      Also notice the (unrelated) error:

      WARNING:asyncio:Loop <_UnixSelectorEventLoop running=False closed=True debug=False> that handles pid 419211 is closed
      

      Further relevant system info: Python 3.11.2 (main, Mar 27 2023, 01:01:40) [GCC 12.2.1 20230201], built using Pyenv on Linux 6.2.9 (Arch, x86_64).

      Am I doing something wrong here or hitting a corner case that's not tackled by this bugfix? Thanks! 🙏🏼

    18. kumaraditya303 commented on Apr 3, 2023

      @kumaraditya303
      kumaraditya303
      on Apr 3, 2023
      Contributor
      More actions

      You are missing await proc.wait() after terminating the process, adding that fixes it. Anyways this is not related to this fix so create a new issue if you still face the issue.

    19. gertvdijk commented on Apr 3, 2023

      @gertvdijk
      gertvdijk
      on Apr 3, 2023
      More actions

      You are missing await proc.wait() after terminating the process, adding that fixes it.

      Are you sure I should? It's also not included in Here’s an example of how asyncio can run a shell command and obtain its result, here: https://docs.python.org/3/library/asyncio-subprocess.html#

      Note that I'm already awaiting the process result with communicate(). For wait() this comment (here) seems to indicate that as well I shouldn't:

      Use the communicate() method when using pipes to avoid this condition.

      Anyways this is not related to this fix so create a new issue if you still face the issue.

      Sorry for the noise then, but to me it sounds exactly the same still.

    20. kumaraditya303 commented on Apr 3, 2023

      @kumaraditya303
      kumaraditya303
      on Apr 3, 2023
      Contributor
      More actions

      You are wrapping proc.communicate() in wait_for so it gets cancelled before it had to chance to call self.wait(). See the source code. The docs are for directly awaiting the proc.communicate() which doesn't work in case of wait_for.

      Reacted by Gert van Dijk
    21. crypto-ninja
      added 2 commits that reference this issue on Dec 11, 2025

      fix: Suppress Linux asyncio cleanup warning (documented)

      b1eefb8

      fix: Suppress Linux asyncio cleanup warning (documented)

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

    Metadata

    Metadata

    Assignees

    • @kumaraditya303
      kumaraditya303

    Labels

    3.11only security fixesonly security fixes3.12only security fixesonly security fixestopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    • asyncio
      Status
      Done
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.