Skip to content

Commit 1f27488

Browse files
authored
fix: do not block asyncio event loop between retries (#805)
1 parent f185c52 commit 1f27488

File tree

3 files changed

+21
-17
lines changed

3 files changed

+21
-17
lines changed

src/strands/event_loop/event_loop.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
4. Manage recursive execution cycles
99
"""
1010

11+
import asyncio
1112
import logging
12-
import time
1313
import uuid
1414
from typing import TYPE_CHECKING, Any, AsyncGenerator
1515

@@ -189,7 +189,7 @@ async def event_loop_cycle(agent: "Agent", invocation_state: dict[str, Any]) ->
189189
MAX_ATTEMPTS,
190190
attempt + 1,
191191
)
192-
time.sleep(current_delay)
192+
await asyncio.sleep(current_delay)
193193
current_delay = min(current_delay * 2, MAX_DELAY)
194194

195195
yield EventLoopThrottleEvent(delay=current_delay)

tests/strands/agent/hooks/test_agent_events.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ async def streaming_tool():
3131

3232

3333
@pytest.fixture
34-
def mock_time():
35-
with unittest.mock.patch.object(strands.event_loop.event_loop, "time") as mock:
34+
def mock_sleep():
35+
with unittest.mock.patch.object(
36+
strands.event_loop.event_loop.asyncio, "sleep", new_callable=unittest.mock.AsyncMock
37+
) as mock:
3638
yield mock
3739

3840

@@ -322,7 +324,7 @@ async def test_stream_e2e_success(alist):
322324

323325

324326
@pytest.mark.asyncio
325-
async def test_stream_e2e_throttle_and_redact(alist, mock_time):
327+
async def test_stream_e2e_throttle_and_redact(alist, mock_sleep):
326328
model = MagicMock()
327329
model.stream.side_effect = [
328330
ModelThrottledException("ThrottlingException | ConverseStream"),
@@ -389,7 +391,7 @@ async def test_stream_e2e_throttle_and_redact(alist, mock_time):
389391
async def test_event_loop_cycle_text_response_throttling_early_end(
390392
agenerator,
391393
alist,
392-
mock_time,
394+
mock_sleep,
393395
):
394396
model = MagicMock()
395397
model.stream.side_effect = [

tests/strands/event_loop/test_event_loop.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626

2727

2828
@pytest.fixture
29-
def mock_time():
30-
with unittest.mock.patch.object(strands.event_loop.event_loop, "time") as mock:
29+
def mock_sleep():
30+
with unittest.mock.patch.object(
31+
strands.event_loop.event_loop.asyncio, "sleep", new_callable=unittest.mock.AsyncMock
32+
) as mock:
3133
yield mock
3234

3335

@@ -186,7 +188,7 @@ async def test_event_loop_cycle_text_response(
186188

187189
@pytest.mark.asyncio
188190
async def test_event_loop_cycle_text_response_throttling(
189-
mock_time,
191+
mock_sleep,
190192
agent,
191193
model,
192194
agenerator,
@@ -215,12 +217,12 @@ async def test_event_loop_cycle_text_response_throttling(
215217

216218
assert tru_stop_reason == exp_stop_reason and tru_message == exp_message and tru_request_state == exp_request_state
217219
# Verify that sleep was called once with the initial delay
218-
mock_time.sleep.assert_called_once()
220+
mock_sleep.assert_called_once()
219221

220222

221223
@pytest.mark.asyncio
222224
async def test_event_loop_cycle_exponential_backoff(
223-
mock_time,
225+
mock_sleep,
224226
agent,
225227
model,
226228
agenerator,
@@ -254,13 +256,13 @@ async def test_event_loop_cycle_exponential_backoff(
254256

255257
# Verify that sleep was called with increasing delays
256258
# Initial delay is 4, then 8, then 16
257-
assert mock_time.sleep.call_count == 3
258-
assert mock_time.sleep.call_args_list == [call(4), call(8), call(16)]
259+
assert mock_sleep.call_count == 3
260+
assert mock_sleep.call_args_list == [call(4), call(8), call(16)]
259261

260262

261263
@pytest.mark.asyncio
262264
async def test_event_loop_cycle_text_response_throttling_exceeded(
263-
mock_time,
265+
mock_sleep,
264266
agent,
265267
model,
266268
alist,
@@ -281,7 +283,7 @@ async def test_event_loop_cycle_text_response_throttling_exceeded(
281283
)
282284
await alist(stream)
283285

284-
mock_time.sleep.assert_has_calls(
286+
mock_sleep.assert_has_calls(
285287
[
286288
call(4),
287289
call(8),
@@ -687,7 +689,7 @@ async def test_event_loop_tracing_with_throttling_exception(
687689
]
688690

689691
# Mock the time.sleep function to speed up the test
690-
with patch("strands.event_loop.event_loop.time.sleep"):
692+
with patch("strands.event_loop.event_loop.asyncio.sleep", new_callable=unittest.mock.AsyncMock):
691693
stream = strands.event_loop.event_loop.event_loop_cycle(
692694
agent=agent,
693695
invocation_state={},
@@ -816,7 +818,7 @@ async def test_prepare_next_cycle_in_tool_execution(agent, model, tool_stream, a
816818

817819

818820
@pytest.mark.asyncio
819-
async def test_event_loop_cycle_exception_model_hooks(mock_time, agent, model, agenerator, alist, hook_provider):
821+
async def test_event_loop_cycle_exception_model_hooks(mock_sleep, agent, model, agenerator, alist, hook_provider):
820822
"""Test that model hooks are correctly emitted even when throttled."""
821823
# Set up the model to raise throttling exceptions multiple times before succeeding
822824
exception = ModelThrottledException("ThrottlingException | ConverseStream")

0 commit comments

Comments
 (0)