Repository navigation
IsolatedAsyncioTestCase does not call asyncio.set_event_loop before setUp anymore, asyncio.Runner+PidFdChildWatcher leaves zombie processes #95736
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Aug 6, 2022 - changed the title
[-]IsolatedAsyncioTestCase does not call asyncio.set_event_loop in setUp anymore in 3.11[/-][+]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` in before `setUp` anymore in 3.11[/+]on Aug 6, 2022 - changed the title
[-]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` in before `setUp` anymore in 3.11[/-][+]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` before `setUp` anymore in 3.11[/+]on Aug 6, 2022 - added3.11only security fixesonly security fixes3.12only security fixesonly security fixes
on Aug 6, 2022 it looks like
asyncio.set_event_loopis also being called an additional time per call toasyncio.Runner.runwhich will callget_child_watcher().attach_loop(loop)every call which causes thePidfdChildWatcherto blow away its callbacksyep that's exactly what's happening here:
import asyncio import sys asyncio.set_child_watcher(asyncio.PidfdChildWatcher()) tasks = set() pids = set() async def subprocess(): proc = await asyncio.create_subprocess_exec( sys.executable, "-c", "import time; print('hello!'); import time; time.sleep(2); print('goodbye')", ) pids.add(proc.pid) print(await proc.communicate()) async def main(): task = asyncio.create_task(subprocess()) tasks.add(task) await asyncio.sleep(1) async def main2(): try: async with asyncio.timeout(3): await asyncio.gather(*tasks) except TimeoutError: import psutil for pid in pids: print(psutil.Process(pid)) else: print("success!") if __name__ == "__main__": with asyncio.Runner() as runner: runner.run(main()) runner.run(main2())
output
hello! goodbye psutil.Process(pid=406749, name='python', status='zombie', started='10:13:40')- changed the title
[-]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` before `setUp` anymore in 3.11[/-][+]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` before `setUp` anymore, asyncio.Runner+PidFdChildWatcher leaves zombie processes[/+]on Aug 6, 2022 - changed the title
[-]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` before `setUp` anymore, asyncio.Runner+PidFdChildWatcher leaves zombie processes[/-][+]`IsolatedAsyncioTestCase` does not call `asyncio.set_event_loop` before `setUp` anymore, `asyncio.Runner`+`PidFdChildWatcher` leaves zombie processes[/+]on Aug 6, 2022 #95584 is vaguely related
The issue here is that
asyncio.Runnercallsset_event_looptoo late and too oftenIn addition child watchers that use attach_loop are prone to unusual behaviour when the main thread switches loop
It looks like IsolatedAsyncioTestCase when the Pidfdchildwatcher is used will break a subprocess started in asyncSetUp
import asyncio import sys import unittest import asyncio asyncio.set_child_watcher(asyncio.PidfdChildWatcher()) def create_free_port(): return 4 # chosen by a fair dice roll class TestProc(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): self.port = create_free_port() self.proc = await asyncio.create_subprocess_exec( sys.executable, "-c", # more realistically this might be an http server or database f"import time; print('listening on {self.port}'); import time; time.sleep(2); print('goodbye')", ) async def testProc(self): print(f"connecting to {self.port}") async def asyncTearDown(self): await self.proc.communicate() # hangs forever on 3.11 if __name__ == "__main__": unittest.main()
on python3.10 this produces:
connecting to 4 listening on 4 goodbye . ---------------------------------------------------------------------- Ran 1 test in 2.022s OKon python3.11 it hangs forever in
await self.proc.communicate()18 remaining items
- added 5 commits that reference this issue
on Aug 17, 2022 @graingert I hope you will make aiohttp compatible with 3.11 regardless of whether this fix gets merged into 3.11. As a base dependency of many other packages it would block 3.11 support for those other packages.
@gvanrossum your question has, as always, helped me crystallize the trade off I chose by accident, and some other options that are available to aiohttp and I'm going to try to enumerate them so that aiohttp can make the right choice here
Whoops, reopening since the 3.11 backport didn’t get merged yet.
- added a commit that references this issue
on Aug 18, 2022
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsTodo
demo reproducer:
see also #93896
it looks like this is because
asyncio.Runnercallsasyncio.set_event_loop(inRunner.run