Support Python 3.14: stop relying on implicit event-loop creation#72
Merged
Conversation
Python 3.14 removed the long-deprecated behaviour where asyncio.get_event_loop() / get_event_loop_policy().get_event_loop() implicitly create and register an event loop for the main thread when none is set; they now raise RuntimeError. Mode resolves Service.loop (and other helpers) outside of a running loop -- notably at import time, when services and agents are declared at module level -- so this broke downstream imports on 3.14 (e.g. faust's `@app.agent()` at module scope). Add a get_event_loop() helper in mode.utils.loops that restores the historical get-or-create semantics in a way that works across 3.9-3.14: return the running loop if any, else the thread's current loop if set, else create and register a new one. Replace the get_event_loop_policy().get_event_loop() call sites across services, threads, locks, futures, logging and the gevent loop with it (and the lone policy.new_event_loop() with asyncio.new_event_loop()). Adds tests/unit/utils/test_loops.py covering the running/current/absent-loop paths. Assisted-by: Claude Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
wbarnha
pushed a commit
to faust-streaming/faust
that referenced
this pull request
Jul 19, 2026
Adds 3.14 to the test matrix, producing required 'Python 3.14/Cython: true' and 'Python 3.14/Cython: false' jobs alongside the existing 3.13 legs. All matrix legs feed the aggregate '✅ Ensure the required checks passing' job, so they become required wherever branch protection requires that check. Python 3.9 was already removed from the matrix and from python_requires (>=3.10); the lingering 'Python 3.9/Cython: *' required status checks exist only in branch protection and must be deleted there. Note: the 3.14 legs need a mode-streaming release carrying the event-loop fix (faust-streaming/mode#72) to pass, since Python 3.14 removed implicit event-loop creation that mode relied on at import time. Assisted-by: Claude Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Python 3.14 removed the implicit creation of an event loop for the main
thread by
asyncio.get_event_loop()/get_event_loop_policy().get_event_loop();they now raise
RuntimeErrorwhen no current loop is set. Mode resolvesService.loopoutside of a running loop (e.g. at import time, when servicesand agents are declared at module scope), which broke imports on 3.14 —
for example faust's
@app.agent()at module level.Adds a
get_event_loop()helper inmode.utils.loopsthat restores thehistorical get-or-create semantics across Python 3.9–3.14 (running loop →
thread's current loop → create + register), and routes every
get_event_loop_policy().get_event_loop()call site through it. New unittests cover the running / current / absent-loop paths. No behaviour change on
3.9–3.13.
Verified: full test suite passes on Python 3.11 and 3.13 (735 passed each), ruff clean, and
Service()instantiation with no current event loop set now succeeds.🤖 Generated with Claude Code
https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
Generated by Claude Code