From 6da50ecc49044cc259f39a7b0fec7ec9fbfd648e Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:32:47 +0100 Subject: [PATCH 1/4] fix(asyncio): preserve producer failure while draining consumer --- src/agents/util/_asyncio_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/util/_asyncio_tasks.py b/src/agents/util/_asyncio_tasks.py index 2974af39f7..8d12c01dc2 100644 --- a/src/agents/util/_asyncio_tasks.py +++ b/src/agents/util/_asyncio_tasks.py @@ -139,7 +139,7 @@ async def run_producer_consumer( try: producer_result = producer_task.result() except BaseException: - await consumer_task + await asyncio.gather(consumer_task, return_exceptions=True) raise consumer_result = await consumer_task From f6228217083424875380efd33352ca84d1cb3069 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:32:58 +0100 Subject: [PATCH 2/4] test(asyncio): cover producer and consumer failure ordering --- tests/test_asyncio_tasks_primary_error.py | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_asyncio_tasks_primary_error.py diff --git a/tests/test_asyncio_tasks_primary_error.py b/tests/test_asyncio_tasks_primary_error.py new file mode 100644 index 0000000000..57a468befa --- /dev/null +++ b/tests/test_asyncio_tasks_primary_error.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import asyncio + +import pytest + +from agents.util._asyncio_tasks import run_producer_consumer + + +@pytest.mark.asyncio +async def test_run_producer_consumer_preserves_producer_failure_when_consumer_also_fails() -> None: + class ProducerError(Exception): + pass + + class ConsumerError(Exception): + pass + + producer_failed = asyncio.Event() + consumer_failed = asyncio.Event() + + async def producer() -> None: + producer_failed.set() + raise ProducerError("producer failed") + + async def consumer() -> None: + await producer_failed.wait() + consumer_failed.set() + raise ConsumerError("consumer failed while draining") + + with pytest.raises(ProducerError, match="producer failed"): + await run_producer_consumer(producer(), consumer()) + + assert consumer_failed.is_set() From 33ebb8e7f2e17db0194c8fa9dd51cd36edf6db54 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:34:27 +0100 Subject: [PATCH 3/4] test(asyncio): make failure ordering deterministic --- tests/test_asyncio_tasks_primary_error.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/test_asyncio_tasks_primary_error.py b/tests/test_asyncio_tasks_primary_error.py index 57a468befa..28fead50f1 100644 --- a/tests/test_asyncio_tasks_primary_error.py +++ b/tests/test_asyncio_tasks_primary_error.py @@ -16,6 +16,7 @@ class ConsumerError(Exception): pass producer_failed = asyncio.Event() + allow_consumer_failure = asyncio.Event() consumer_failed = asyncio.Event() async def producer() -> None: @@ -23,11 +24,17 @@ async def producer() -> None: raise ProducerError("producer failed") async def consumer() -> None: - await producer_failed.wait() + await allow_consumer_failure.wait() consumer_failed.set() raise ConsumerError("consumer failed while draining") + task = asyncio.create_task(run_producer_consumer(producer(), consumer())) + await producer_failed.wait() + await asyncio.sleep(0) + assert not task.done() + + allow_consumer_failure.set() with pytest.raises(ProducerError, match="producer failed"): - await run_producer_consumer(producer(), consumer()) + await task assert consumer_failed.is_set() From d9d5708943055e70ed9969e74a12905d8e9fb012 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:34:58 +0100 Subject: [PATCH 4/4] test(asyncio): let producer failure enter drain path --- tests/test_asyncio_tasks_primary_error.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_asyncio_tasks_primary_error.py b/tests/test_asyncio_tasks_primary_error.py index 28fead50f1..9c3d5c54c4 100644 --- a/tests/test_asyncio_tasks_primary_error.py +++ b/tests/test_asyncio_tasks_primary_error.py @@ -31,6 +31,7 @@ async def consumer() -> None: task = asyncio.create_task(run_producer_consumer(producer(), consumer())) await producer_failed.wait() await asyncio.sleep(0) + await asyncio.sleep(0) assert not task.done() allow_consumer_failure.set()