tornado: Call close() on Django HttpResponse objects.

This is necessary to break the uncollectable reference cycle created
by our ‘request_notes.saved_response = json_response(…)’, Django’s
‘response._resource_closers.append(request.close)’, and Python’s
https://bugs.python.org/issue44680.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2021-07-19 22:49:38 -07:00 committed by Tim Abbott
parent 7cf859882d
commit fd0ab7c4ec
1 changed files with 14 additions and 20 deletions

View File

@ -148,10 +148,9 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler):
def get(self, *args: Any, **kwargs: Any) -> None:
request = self.convert_tornado_request_to_django_request()
response = self.get_response(request)
try:
response = self.get_response(request)
if hasattr(response, "asynchronous"):
# We import async_request_timer_restart during runtime
# to avoid cyclic dependency with zerver.lib.request
@ -165,22 +164,21 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler):
# consumed by the request when it eventually returns a
# response and is logged.
async_request_timer_stop(request)
return
else:
# For normal/synchronous requests that don't end up
# long-polling, we just need to write the HTTP
# response that Django prepared for us via Tornado.
# Mark this handler ID as finished for Zulip's own tracking.
clear_handler_by_id(self.handler_id)
self.write_django_response_as_tornado_response(response)
finally:
# Tell Django that we're done processing this request on
# the Django side; this triggers cleanup work like
# resetting the urlconf and any cache/database
# connections.
signals.request_finished.send(sender=self.__class__)
# For normal/synchronous requests that don't end up
# long-polling, we fall through to here and just need to write
# the HTTP response that Django prepared for us via Tornado.
# Mark this handler ID as finished for Zulip's own tracking.
clear_handler_by_id(self.handler_id)
self.write_django_response_as_tornado_response(response)
response.close()
def head(self, *args: Any, **kwargs: Any) -> None:
self.get(*args, **kwargs)
@ -258,16 +256,12 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler):
res_type=result_dict["result"], data=result_dict, status=self.get_status()
)
response = self.get_response(request)
try:
response = self.get_response(request)
# Explicitly mark requests as varying by cookie, since the
# middleware will not have seen a session access
patch_vary_headers(response, ("Cookie",))
self.write_django_response_as_tornado_response(response)
finally:
# Tell Django we're done processing this request
#
# TODO: Investigate whether this (and other call points in
# this file) should be using response.close() instead.
signals.request_finished.send(sender=self.__class__)
self.write_django_response_as_tornado_response(response)
response.close()