Cache get_event_loop()'s per-thread loop instead of re-resolving every call#76
Merged
Merged
Conversation
…y call get_event_loop() previously fell all the way through to asyncio.get_event_loop() (raise-then-catch RuntimeError, then new_event_loop()+set_event_loop()) on every single call once there was no running loop -- the common case for calls made outside of a running loop, e.g. at import time when services/agents are declared at module level. Cache the resolved loop in thread-local storage and reuse it on later calls from the same thread, skipping the asyncio.get_event_loop() dance entirely once a loop has been resolved. asyncio.get_running_loop() is still checked unconditionally on every call and can't be cached -- whether a loop is currently *running* changes on every call by definition -- but that's the cheap path (no exception when a loop is actually running); the expensive not-running path is what benefits from caching. The cache must be thread-local, not a single global/module-level singleton: ServiceThread runs a dedicated event loop per worker thread (mode/threads.py), including code that compares get_event_loop()'s result by identity against a specific thread's loop. A plain global would return the wrong thread's loop across worker threads. threading.local() gives each thread independent storage with no cross-thread leakage and no explicit cleanup needed when a thread exits. Adds tests: caching skips a repeated asyncio.get_event_loop() call, and a second thread's cached loop is independent of (and doesn't leak into) the main thread's. Assisted-by: Claude Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
wbarnha
force-pushed
the
claude/faust-mode-open-prs-ur3hqs
branch
2 times, most recently
from
July 19, 2026 17:17
e8a35b4 to
5dda6c9
Compare
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
get_event_loop()fell all the way through toasyncio.get_event_loop()(raise-then-catchRuntimeError, thennew_event_loop()+set_event_loop()) on every single call once there was no running loop — the common case for calls made outside of a running loop, e.g. at import time when services/agents are declared at module level.Cache the resolved loop in thread-local storage and reuse it on later calls from the same thread, skipping the
asyncio.get_event_loop()dance entirely once a loop has been resolved.asyncio.get_running_loop()is still checked unconditionally on every call and can't be cached — whether a loop is currently running changes on every call by definition — but that's the cheap path (no exception when a loop is actually running); the expensive not-running path is what benefits from caching.The cache is thread-local (
threading.local()), not a single global/module-level singleton:ServiceThreadruns a dedicated event loop per worker thread (mode/threads.py), including code that comparesget_event_loop()'s result by identity against a specific thread's loop (if get_event_loop() is self.parent_loop). A plain global would return the wrong thread's loop across worker threads.Verification
asyncio.get_event_loop()call, and a second thread's cached loop is independent of (and doesn't leak into) the main thread's.--random-orderto rule out cache-staleness order-dependence.faust-streaming/faustcheckout (same setup used to verify Fix two annotation-resolution regressions from the 3.13 rewrite #75): fulltests/unit+tests/functional— 2128 passed, 32 skipped, 0 failed, unchanged from before this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
Generated by Claude Code