Skip to content

fix: handle Python 3.12+ get_event_loop RuntimeError in VirtualKernelContext#1156

Open
maartenbreddels wants to merge 4 commits intomasterfrom
fix/event-loop-3.12-kernel-context
Open

fix: handle Python 3.12+ get_event_loop RuntimeError in VirtualKernelContext#1156
maartenbreddels wants to merge 4 commits intomasterfrom
fix/event-loop-3.12-kernel-context

Conversation

@maartenbreddels
Copy link
Copy Markdown
Contributor

Summary

Unit tests on master are failing on Python 3.12 across macOS and Ubuntu with:

RuntimeError: There is no current event loop in thread 'MainThread'.

Every test after tests/unit/starlette_lifespan_test.py (~30% of the suite) errors out at setup of the autouse kernel_context fixture.

Why

tests/unit/starlette_lifespan_test.py (added in 1a19f8a) calls asyncio.run(...). On Python 3.12+, asyncio.run() cleans up by calling set_event_loop(None), which puts the event-loop policy into a state where a subsequent asyncio.get_event_loop() call from the main thread raises RuntimeError instead of silently creating a new loop.

VirtualKernelContext.event_loop used asyncio.get_event_loop as its default_factory, so every test after the lifespan test failed when the kernel_context fixture constructed a new VirtualKernelContext.

Fix

Wrap the factory in a small helper that falls back to new_event_loop() + set_event_loop() when get_event_loop() raises. This matches the pre-3.12 auto-create behavior that the rest of the code was implicitly relying on.

Reproducer on 3.12:

import asyncio
async def dummy(): pass
asyncio.run(dummy())
asyncio.get_event_loop()  # RuntimeError: There is no current event loop in thread 'MainThread'

Test plan

  • CI unit tests pass on Python 3.12 (previously failed)
  • CI unit tests continue to pass on Python 3.8

🤖 Generated with Claude Code

…alKernelContext

On Python 3.12+, asyncio.run() calls set_event_loop(None) in cleanup, which puts
the policy into a state where a subsequent asyncio.get_event_loop() raises
RuntimeError instead of implicitly creating a new loop.

The VirtualKernelContext.event_loop field used asyncio.get_event_loop as its
default_factory, so any test running after a test that used asyncio.run() (e.g.
starlette_lifespan_test) failed in the autouse kernel_context fixture that
constructs a VirtualKernelContext.

Wrap the factory to create and set a new loop when get_event_loop() raises.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Windows unit-test jobs were hanging for hours after pytest finished
successfully — the shell step never received "process completed" because
a lingering thread/process kept Python alive. Without a job timeout, the
runner waits the full default (6h).

- timeout-minutes: 15 on the unit-test job so a hang fails fast instead
  of sitting for hours.
- --session-timeout=600 makes pytest-timeout abort the whole session and
  dump a stack trace before the job timeout fires, so we see what hung.
- -v prints test names as they run, so the last test before a hang is
  visible in the log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Windows CI the Python process hangs for hours after pytest prints
its passing summary, because non-daemon threads from ipykernel/websockets
keep the interpreter alive. Force-exit in pytest_sessionfinish so the
shell step completes with pytest's exit code.

Gated on CI=true so local runs still cleanup normally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 0.001s bound was too tight for CI runners — observed delta of
0.0016s on mac 3.8 CI. 0.05s still clearly distinguishes "cancelled
immediately" from "waited out the ~0.05s remaining cull window".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant