From 39ff02b6e4b2f3af9eafa52a2e9ba5d4fd5009e5 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Mon, 6 Aug 2018 14:12:30 +1000 Subject: [PATCH] Modifications the `handle_request` function to detect and gracefully handle the case that the request_handler Task is canceled by the sanic server while it is handling the request. One common occurrence of this is when the server issues a ResponseTimeout error, it also cancels the response_handler Task. The Canceled exception handler purposely sets `response` to `None` to drop references to the handler coroutine, in an attempt to preemptively release resources. This commit also fixes a possible reference-before-assignment of the `response` variable in the `handle_request` function. Finally, another byproduct of this change is that ResponseMiddleware will no longer run if the `response` is `None`. --- sanic/app.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index f9eda2d4..e4e4ff20 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -571,6 +571,10 @@ class Sanic: :return: Nothing """ + # Define `response` var here to remove warnings about + # allocation before assignment below. + response = None + cancelled = False try: # -------------------------------------------- # # Request Middleware @@ -597,6 +601,13 @@ class Sanic: response = handler(request, *args, **kwargs) if isawaitable(response): response = await response + except CancelledError: + # If response handler times out, the server handles the error + # and cancels the handle_request job. + # In this case, the transport is already closed and we cannot + # issue a response. + response = None + cancelled = True except Exception as e: # -------------------------------------------- # # Response Generation Failed @@ -622,13 +633,22 @@ class Sanic: # -------------------------------------------- # # Response Middleware # -------------------------------------------- # - try: - response = await self._run_response_middleware(request, - response) - except BaseException: - error_logger.exception( - 'Exception occurred in one of response middleware handlers' - ) + # Don't run response middleware if response is None + if response is not None: + try: + response = await self._run_response_middleware(request, + response) + except CancelledError: + # Response middleware can timeout too, as above. + response = None + cancelled = True + except BaseException: + error_logger.exception( + 'Exception occurred in one of response ' + 'middleware handlers' + ) + if cancelled: + raise CancelledError() # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse):