Skip to content

Cache get_event_loop()'s per-thread loop instead of re-resolving every call#76

Merged
wbarnha merged 1 commit into
masterfrom
claude/faust-mode-open-prs-ur3hqs
Jul 19, 2026
Merged

Cache get_event_loop()'s per-thread loop instead of re-resolving every call#76
wbarnha merged 1 commit into
masterfrom
claude/faust-mode-open-prs-ur3hqs

Conversation

@wbarnha

@wbarnha wbarnha commented Jul 19, 2026

Copy link
Copy Markdown
Member

Description

get_event_loop() 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 is thread-local (threading.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 (if get_event_loop() is self.parent_loop). A plain global would return the wrong thread's loop across worker threads.

Verification

  • Added 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.
  • Full suite: 740 passed on Python 3.11 and 3.13, ruff clean. Loop and thread-related tests re-run repeatedly with --random-order to rule out cache-staleness order-dependence.
  • Cross-checked against a real faust-streaming/faust checkout (same setup used to verify Fix two annotation-resolution regressions from the 3.13 rewrite #75): full tests/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

…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
wbarnha force-pushed the claude/faust-mode-open-prs-ur3hqs branch 2 times, most recently from e8a35b4 to 5dda6c9 Compare July 19, 2026 17:17
@wbarnha
wbarnha merged commit c30f619 into master Jul 19, 2026
25 checks passed
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.

2 participants