-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Revert broken async sleep helpers in execution_mixin.py #1279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,45 @@ | |||||||||||
| import asyncio | ||||||||||||
| import threading | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def _async_safe_sleep(seconds: float): | ||||||||||||
| """Async-safe sleep that uses asyncio.sleep when in an event loop.""" | ||||||||||||
| try: | ||||||||||||
| # Check if there's a running event loop | ||||||||||||
| loop = asyncio.get_running_loop() | ||||||||||||
| if loop.is_running(): | ||||||||||||
| # We're in an async context, use asyncio.sleep | ||||||||||||
| return asyncio.create_task(asyncio.sleep(seconds)) | ||||||||||||
| except RuntimeError: | ||||||||||||
| # No running event loop, we're in sync context | ||||||||||||
| pass | ||||||||||||
|
|
||||||||||||
| # Fallback to regular time.sleep for sync contexts | ||||||||||||
| time.sleep(seconds) | ||||||||||||
| return None | ||||||||||||
|
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| def _smart_sleep(seconds: float): | ||||||||||||
| """Smart sleep that detects async context and uses appropriate sleep method. | ||||||||||||
|
|
||||||||||||
| Returns a coroutine if in async context, otherwise sleeps synchronously. | ||||||||||||
| """ | ||||||||||||
| try: | ||||||||||||
| # Check if there's a running event loop | ||||||||||||
| loop = asyncio.get_running_loop() | ||||||||||||
| if loop.is_running(): | ||||||||||||
| # We're in an async context, return a coroutine | ||||||||||||
| async def async_sleep(): | ||||||||||||
| await asyncio.sleep(seconds) | ||||||||||||
| return async_sleep() | ||||||||||||
| except RuntimeError: | ||||||||||||
| # No running event loop, we're in sync context | ||||||||||||
| pass | ||||||||||||
|
|
||||||||||||
| # Sync context - use regular time.sleep | ||||||||||||
| time.sleep(seconds) | ||||||||||||
| return None | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Returned coroutine is never awaited, sleep won't occur in async contexts. When a running event loop is detected, this function returns a coroutine object but the caller never awaits it. Since all call sites (lines 995, 998, 1027, 1036, 1095, 1100) are in sync methods that simply call To properly block synchronously while not blocking the event loop, use 🐛 Proposed fix using run_coroutine_threadsafe def _smart_sleep(seconds: float):
"""Smart sleep that detects async context and uses appropriate sleep method.
- Returns a coroutine if in async context, otherwise sleeps synchronously.
+ Sleeps without blocking the event loop when called from a sync context
+ while an async loop is running in another thread.
"""
try:
# Check if there's a running event loop
loop = asyncio.get_running_loop()
if loop.is_running():
- # We're in an async context, return a coroutine
- async def async_sleep():
- await asyncio.sleep(seconds)
- return async_sleep()
+ # Schedule async sleep on the running loop and wait for completion
+ future = asyncio.run_coroutine_threadsafe(asyncio.sleep(seconds), loop)
+ future.result() # Block until sleep completes
+ return
except RuntimeError:
# No running event loop, we're in sync context
pass
# Sync context - use regular time.sleep
time.sleep(seconds)
- return NoneBased on learnings: "All I/O operations must have both sync and async variants; never block the event loop with sync I/O in async context." 🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| # Fallback helpers to avoid circular imports | ||||||||||||
| def _get_console(): | ||||||||||||
| from rich.console import Console | ||||||||||||
|
|
@@ -953,10 +992,10 @@ def run_server(): | |||||||||||
| server_thread.start() | ||||||||||||
|
|
||||||||||||
| # Wait for a moment to allow the server to start and register endpoints | ||||||||||||
| time.sleep(0.5) | ||||||||||||
| _smart_sleep(0.5) | ||||||||||||
| else: | ||||||||||||
| # If server is already running, wait a moment to make sure the endpoint is registered | ||||||||||||
| time.sleep(0.1) | ||||||||||||
| _smart_sleep(0.1) | ||||||||||||
| print(f"🔌 Available endpoints on port {port}: {', '.join(list(_registered_agents[port].keys()))}") | ||||||||||||
|
|
||||||||||||
| # Get the stack frame to check if this is the last launch() call in the script | ||||||||||||
|
|
@@ -985,7 +1024,7 @@ def run_server(): | |||||||||||
| try: | ||||||||||||
| print("\nAll agents registered for HTTP mode. Press Ctrl+C to stop the servers.") | ||||||||||||
| while True: | ||||||||||||
| time.sleep(1) | ||||||||||||
| _smart_sleep(1) | ||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Suggested change
qodo-code-review[bot] marked this conversation as resolved.
Outdated
|
||||||||||||
| except KeyboardInterrupt: | ||||||||||||
| print("\nServers stopped") | ||||||||||||
| except Exception as e: | ||||||||||||
|
|
@@ -994,7 +1033,7 @@ def run_server(): | |||||||||||
| try: | ||||||||||||
| print("\nKeeping HTTP servers alive. Press Ctrl+C to stop.") | ||||||||||||
| while True: | ||||||||||||
| time.sleep(1) | ||||||||||||
| _smart_sleep(1) | ||||||||||||
| except KeyboardInterrupt: | ||||||||||||
| print("\nServers stopped") | ||||||||||||
| return None | ||||||||||||
|
|
@@ -1053,12 +1092,12 @@ def run_mcp_server(): | |||||||||||
|
|
||||||||||||
| server_thread = threading.Thread(target=run_mcp_server, daemon=True) | ||||||||||||
| server_thread.start() | ||||||||||||
| time.sleep(0.5) | ||||||||||||
| _smart_sleep(0.5) | ||||||||||||
|
|
||||||||||||
| try: | ||||||||||||
| print("\nKeeping MCP server alive. Press Ctrl+C to stop.") | ||||||||||||
| while True: | ||||||||||||
| time.sleep(1) | ||||||||||||
| _smart_sleep(1) | ||||||||||||
| except KeyboardInterrupt: | ||||||||||||
| print("\nMCP Server stopped") | ||||||||||||
| return None | ||||||||||||
|
|
||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of
_async_safe_sleepis problematic for synchronous callers. When an event loop is running, it returns anasyncio.Taskand continues execution immediately. Since the calling code in this file is synchronous and does not (and cannot) await this task, the execution flow is not actually paused. This is not a valid replacement fortime.sleep()if the intention is to wait before proceeding to the next line of code.