diff --git a/src/hypercorn/asyncio/task_group.py b/src/hypercorn/asyncio/task_group.py index 4bd91a7..0030ca6 100644 --- a/src/hypercorn/asyncio/task_group.py +++ b/src/hypercorn/asyncio/task_group.py @@ -27,10 +27,15 @@ async def _handle( try: await app(scope, receive, send, sync_spawn, call_soon) except asyncio.CancelledError: + try: + await send(None) + except Exception: + pass raise except Exception: await config.log.exception("Error in ASGI Framework") - finally: + await send(None) + else: await send(None) diff --git a/src/hypercorn/trio/task_group.py b/src/hypercorn/trio/task_group.py index 8dd8b20..7adcf9b 100644 --- a/src/hypercorn/trio/task_group.py +++ b/src/hypercorn/trio/task_group.py @@ -27,6 +27,10 @@ async def _handle( try: await app(scope, receive, send, sync_spawn, call_soon) except trio.Cancelled: + try: + await send(None) + except Exception: + pass raise except BaseExceptionGroup as error: _, other_errors = error.split(trio.Cancelled) @@ -37,7 +41,8 @@ async def _handle( raise except Exception: await config.log.exception("Error in ASGI Framework") - finally: + await send(None) + else: await send(None) diff --git a/tests/asyncio/test_task_group.py b/tests/asyncio/test_task_group.py index b432b27..f92c284 100644 --- a/tests/asyncio/test_task_group.py +++ b/tests/asyncio/test_task_group.py @@ -43,3 +43,30 @@ async def _error_app(scope: Scope, receive: Callable, send: Callable) -> None: async with TaskGroup(event_loop) as task_group: await task_group.spawn_app(ASGIWrapper(_error_app), Config(), http_scope, app_queue.put) assert (await app_queue.get()) is None + + +@pytest.mark.asyncio +async def test_handle_cancel_ignores_send_failure(http_scope: HTTPScope) -> None: + from hypercorn.asyncio.task_group import _handle + + async def _hang_app(scope: Scope, receive: Callable, send: Callable) -> None: + await asyncio.Event().wait() + + async def _failing_send(message: object) -> None: + raise AssertionError("cannot call write() after reset()") + + task = asyncio.create_task( + _handle( + ASGIWrapper(_hang_app), + Config(), + http_scope, + None, # type: ignore[arg-type] + _failing_send, + None, # type: ignore[arg-type] + None, # type: ignore[arg-type] + ) + ) + await asyncio.sleep(0) + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task