Refs #36315 -- Used contextlib.closing() in ASGIHandler.handle().

This commit is contained in:
Thomas Grainger 2025-11-07 11:13:36 -05:00 committed by Jacob Walls
parent 5ef870fbc5
commit 796cf3d325

View file

@ -3,7 +3,7 @@ import logging
import sys import sys
import tempfile import tempfile
import traceback import traceback
from contextlib import aclosing from contextlib import aclosing, closing
from asgiref.sync import ThreadSensitiveContext, sync_to_async from asgiref.sync import ThreadSensitiveContext, sync_to_async
@ -174,65 +174,65 @@ class ASGIHandler(base.BaseHandler):
body_file = await self.read_body(receive) body_file = await self.read_body(receive)
except RequestAborted: except RequestAborted:
return return
# Request is complete and can be served.
set_script_prefix(get_script_prefix(scope))
await signals.request_started.asend(sender=self.__class__, scope=scope)
# Get the request and check for basic issues.
request, error_response = self.create_request(scope, body_file)
if request is None:
body_file.close()
await self.send_response(error_response, send)
await sync_to_async(error_response.close)()
return
async def process_request(request, send): with closing(body_file):
response = await self.run_get_response(request) # Request is complete and can be served.
try: set_script_prefix(get_script_prefix(scope))
await self.send_response(response, send) await signals.request_started.asend(sender=self.__class__, scope=scope)
except asyncio.CancelledError: # Get the request and check for basic issues.
# Client disconnected during send_response (ignore exception). request, error_response = self.create_request(scope, body_file)
pass if request is None:
body_file.close()
await self.send_response(error_response, send)
await sync_to_async(error_response.close)()
return
return response async def process_request(request, send):
response = await self.run_get_response(request)
# Try to catch a disconnect while getting response.
tasks = [
# Check the status of these tasks and (optionally) terminate them
# in this order. The listen_for_disconnect() task goes first
# because it should not raise unexpected errors that would prevent
# us from cancelling process_request().
asyncio.create_task(self.listen_for_disconnect(receive)),
asyncio.create_task(process_request(request, send)),
]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Now wait on both tasks (they may have both finished by now).
for task in tasks:
if task.done():
try: try:
task.result() await self.send_response(response, send)
except RequestAborted:
# Ignore client disconnects.
pass
except AssertionError:
body_file.close()
raise
else:
# Allow views to handle cancellation.
task.cancel()
try:
await task
except asyncio.CancelledError: except asyncio.CancelledError:
# Task re-raised the CancelledError as expected. # Client disconnected during send_response (ignore exception).
pass pass
try: return response
response = tasks[1].result()
except asyncio.CancelledError:
await signals.request_finished.asend(sender=self.__class__)
else:
await sync_to_async(response.close)()
body_file.close() # Try to catch a disconnect while getting response.
tasks = [
# Check the status of these tasks and (optionally) terminate them
# in this order. The listen_for_disconnect() task goes first
# because it should not raise unexpected errors that would prevent
# us from cancelling process_request().
asyncio.create_task(self.listen_for_disconnect(receive)),
asyncio.create_task(process_request(request, send)),
]
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
# Now wait on both tasks (they may have both finished by now).
for task in tasks:
if task.done():
try:
task.result()
except RequestAborted:
# Ignore client disconnects.
pass
except AssertionError:
body_file.close()
raise
else:
# Allow views to handle cancellation.
task.cancel()
try:
await task
except asyncio.CancelledError:
# Task re-raised the CancelledError as expected.
pass
try:
response = tasks[1].result()
except asyncio.CancelledError:
await signals.request_finished.asend(sender=self.__class__)
else:
await sync_to_async(response.close)()
async def listen_for_disconnect(self, receive): async def listen_for_disconnect(self, receive):
"""Listen for disconnect from the client.""" """Listen for disconnect from the client."""