From 36032cc26e082beaa569b2be5bc5d88f9249f60e Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Thu, 11 Oct 2018 22:38:26 -0700 Subject: [PATCH 1/3] cancel task when connection_lost --- sanic/server.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sanic/server.py b/sanic/server.py index 5668c5c4..8a963203 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -120,6 +120,10 @@ class HttpProtocol(asyncio.Protocol): def connection_lost(self, exc): self.connections.discard(self) + if self._request_handler_task: + self._request_handler_task.cancel() + if self._request_stream_task: + self._request_stream_task.cancel() if self._request_timeout_handler: self._request_timeout_handler.cancel() if self._response_timeout_handler: From 8b13597099ee71a79aa326358160af57b7489e74 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Thu, 11 Oct 2018 23:02:21 -0700 Subject: [PATCH 2/3] add unit tests for verifying --- tests/test_request_cancel.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/test_request_cancel.py diff --git a/tests/test_request_cancel.py b/tests/test_request_cancel.py new file mode 100644 index 00000000..eb8ce9b5 --- /dev/null +++ b/tests/test_request_cancel.py @@ -0,0 +1,34 @@ +import pytest +import asyncio +import contextlib + +from sanic.response import text + + +async def test_request_cancel_when_connection_lost(loop, app, test_client): + app.still_serving_cancelled_request = False + + @app.get('/') + async def handler(request): + await asyncio.sleep(1.0) + # at this point client is already disconnected + app.still_serving_cancelled_request = True + return text('OK') + + test_cli = await test_client(app) + + # schedule client call + task = loop.create_task(test_cli.get('/')) + loop.call_later(0.01, task) + await asyncio.sleep(0.5) + + # cancelling request and closing connection after 0.5 sec + task.cancel() + + with contextlib.suppress(asyncio.CancelledError): + await task + + # Wait for server and check if it's still serving the cancelled request + await asyncio.sleep(1.0) + + assert app.still_serving_cancelled_request is False From 3149d5a66d992f88ea6f1060dca6713c99696aae Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Thu, 11 Oct 2018 23:12:33 -0700 Subject: [PATCH 3/3] add unit test for request_stream --- tests/test_request_cancel.py | 41 +++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/test_request_cancel.py b/tests/test_request_cancel.py index eb8ce9b5..56610b2d 100644 --- a/tests/test_request_cancel.py +++ b/tests/test_request_cancel.py @@ -2,7 +2,7 @@ import pytest import asyncio import contextlib -from sanic.response import text +from sanic.response import text, stream async def test_request_cancel_when_connection_lost(loop, app, test_client): @@ -32,3 +32,42 @@ async def test_request_cancel_when_connection_lost(loop, app, test_client): await asyncio.sleep(1.0) assert app.still_serving_cancelled_request is False + + +async def test_stream_request_cancel_when_connection_lost(loop, app, test_client): + app.still_serving_cancelled_request = False + + @app.post('/post/', stream=True) + async def post(request, id): + assert isinstance(request.stream, asyncio.Queue) + + async def streaming(response): + while True: + body = await request.stream.get() + if body is None: + break + await response.write(body.decode('utf-8')) + + await asyncio.sleep(1.0) + # at this point client is already disconnected + app.still_serving_cancelled_request = True + + return stream(streaming) + + test_cli = await test_client(app) + + # schedule client call + task = loop.create_task(test_cli.post('/post/1')) + loop.call_later(0.01, task) + await asyncio.sleep(0.5) + + # cancelling request and closing connection after 0.5 sec + task.cancel() + + with contextlib.suppress(asyncio.CancelledError): + await task + + # Wait for server and check if it's still serving the cancelled request + await asyncio.sleep(1.0) + + assert app.still_serving_cancelled_request is False