refactor(core): simplify HTTP client lifecycle with event loop cleanup#823
refactor(core): simplify HTTP client lifecycle with event loop cleanup#823
Conversation
Summary of ChangesHello @garrett4wade, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical issue where Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively refactors the HTTP client management to address potential issues with stale event loop references in aiohttp.ClientSession. The move from a singleton/threading.local() approach to a global dictionary of per-thread HttpClientManager instances, keyed by thread ID, is a solid solution. The introduction of initialization hooks in AsyncTaskRunner is a key part of this fix, ensuring that HTTP clients are configured within the correct background thread and event loop. The changes are well-contained and consistently applied across the codebase, including necessary updates to tests. I have one suggestion to improve the robustness of the new initialization hook registration.
b06a3ff to
46cb88e
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request is a significant and well-motivated refactoring of the HTTP client lifecycle management. Moving from a custom shutdown hook to an asyncio-atexit pattern simplifies the architecture and improves thread safety, which is a great improvement. The changes are clean and the decoupling of WorkflowExecutor from the client lifecycle is a good design choice. I've found a critical memory leak in the new event loop cleanup utility and a potential unhandled exception that could crash operations. I've provided detailed comments and code suggestions to address these issues. Once these are fixed, this will be an excellent contribution.
Resolved Review CommentsI've addressed all the critical and high-priority issues raised by @gemini-code-assist: 1. ✅ Fixed Memory Leak in
|
|
Thanks for the update, @garrett4wade! It's great to see that the critical and high-priority issues have been addressed. The detailed breakdown of the fixes for the memory leak, |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request is a great refactoring that simplifies HTTP client lifecycle management by moving from a custom shutdown hook to a more idiomatic asyncio-atexit pattern. The changes significantly improve thread safety by introducing a per-thread HttpClientManager and enhance robustness by handling event loop changes gracefully. The new register_loop_cleanup utility is a clean and effective way to manage resource cleanup within the event loop's lifecycle.
I've identified a potential resource leak issue in the stale client cleanup logic and an opportunity to reduce code duplication for better maintainability. Overall, this is a high-quality contribution that reduces complexity and improves the codebase.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request is a well-executed refactoring that simplifies HTTP client lifecycle management. It successfully replaces the custom shutdown hook mechanism with a more idiomatic asyncio-atexit pattern, improving thread safety and reducing complexity. The introduction of a per-thread HttpClientManager and the new register_loop_cleanup utility are excellent changes. The overall implementation is robust and clean. I have a couple of minor suggestions to make the cleanup logic in workflow_context.py slightly more concise.
Resolved High-Priority Resource Leak IssueI've addressed the resource leak concern raised in the latest review: ✅ Fixed State Reset Ordering (High Priority - Line 107)Issue: State was being reset ( Fix: Moved the state reset to after the close operations. Now the cleanup logic:
This ensures we don't lose track of unclosed client objects even if exceptions occur during the close operations. Note: I'm leaving the code duplication issue unresolved as it's acceptable in this context for clarity and maintainability. |
Replace AsyncTaskRunner shutdown hooks with asyncio-atexit pattern for HTTP client cleanup: - Remove shutdown hook mechanism from AsyncTaskRunner (52 lines) and related tests (128 lines) - Implement per-thread HttpClientManager with event loop cleanup registration - Add register_loop_cleanup utility in concurrent.py for atexit-like behavior on loop closure - Fix event loop change detection and stale client handling - Make get_aiohttp_session/get_httpx_client async to properly detect running loop - Remove WorkflowExecutor coupling to HTTP client lifecycle This simplifies the architecture by moving cleanup responsibility from AsyncTaskRunner to the event loop itself, reducing complexity and improving testability. Co-Authored-By: Claude <noreply@anthropic.com>
…op cleanup Address code review feedback from Gemini Code Assist: - Fix memory leak in _LoopCleanupEntry by removing circular reference through lambda closure - Simplify _LoopCleanupEntry to only store callbacks, removing unnecessary _close_ref logic - Update _patched_loop_close to call loop._cleanup_orig_close directly - Add error handling when closing stale HTTP clients from different event loops These changes improve memory management and robustness when event loops are reused or changed. Co-Authored-By: Claude <noreply@anthropic.com>
…leaks Address high-priority review comment from Gemini Code Assist: - Move state reset (_aiohttp_session, _httpx_client, _event_loop = None) to after the close() operations instead of before - This prevents inconsistent state if close() raises an exception - Ensures we don't lose track of unclosed client objects, avoiding resource leaks Previously, if an exception occurred during close(), the state would be reset but the client might not have been closed, leading to resource leaks. Co-Authored-By: Claude <noreply@anthropic.com>
9859d16 to
344cec8
Compare
inclusionAI#823) * refactor(core): simplify HTTP client lifecycle with event loop cleanup Replace AsyncTaskRunner shutdown hooks with asyncio-atexit pattern for HTTP client cleanup: - Remove shutdown hook mechanism from AsyncTaskRunner (52 lines) and related tests (128 lines) - Implement per-thread HttpClientManager with event loop cleanup registration - Add register_loop_cleanup utility in concurrent.py for atexit-like behavior on loop closure - Fix event loop change detection and stale client handling - Make get_aiohttp_session/get_httpx_client async to properly detect running loop - Remove WorkflowExecutor coupling to HTTP client lifecycle This simplifies the architecture by moving cleanup responsibility from AsyncTaskRunner to the event loop itself, reducing complexity and improving testability. Co-Authored-By: Claude <noreply@anthropic.com> * fix(core): address memory leak and improve error handling in event loop cleanup Address code review feedback from Gemini Code Assist: - Fix memory leak in _LoopCleanupEntry by removing circular reference through lambda closure - Simplify _LoopCleanupEntry to only store callbacks, removing unnecessary _close_ref logic - Update _patched_loop_close to call loop._cleanup_orig_close directly - Add error handling when closing stale HTTP clients from different event loops These changes improve memory management and robustness when event loops are reused or changed. Co-Authored-By: Claude <noreply@anthropic.com> * fix(core): move state reset after client closure to prevent resource leaks Address high-priority review comment from Gemini Code Assist: - Move state reset (_aiohttp_session, _httpx_client, _event_loop = None) to after the close() operations instead of before - This prevents inconsistent state if close() raises an exception - Ensures we don't lose track of unclosed client objects, avoiding resource leaks Previously, if an exception occurred during close(), the state would be reset but the client might not have been closed, leading to resource leaks. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Description
This PR refactors HTTP client lifecycle management by replacing the AsyncTaskRunner shutdown hook mechanism with an asyncio-atexit pattern. The changes simplify the architecture by moving cleanup responsibility from AsyncTaskRunner to the event loop itself.
Key improvements:
register_loop_cleanuputility inareal/utils/concurrent.pyfor atexit-like behavior on loop closureget_aiohttp_sessionandget_httpx_clientasync to properly detect running loopRelated Issue
Fixes thread-safety issues in HTTP client management where sessions could be reused across threads or event loops.
Type of Change
Checklist
Additional Context
This change reduces overall complexity (net -19 lines) while improving thread safety and testability. The asyncio-atexit pattern is more idiomatic for async resource cleanup than custom shutdown hooks.
The changes affect:
areal/core/async_task_runner.py- Removed shutdown hooksareal/core/workflow_context.py- Per-thread HttpClientManager with loop cleanupareal/core/remote_inf_engine.py- Updated to await async session getterareal/core/workflow_executor.py- Removed HTTP client configurationareal/utils/concurrent.py- Added event loop cleanup utilitiesareal/tests/test_async_task_runner.py- Removed shutdown hook testsNeed help? Check the Contributing Guide or ask in GitHub Discussions!