request: Weaken ZulipRequestNotes.tornado_handler reference.

This prevents a memory leak arising from Python’s inability to collect
a reference cycle from a WeakKeyDictionary value to its key
(https://bugs.python.org/issue44680).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2021-07-19 14:41:37 -07:00
committed by Tim Abbott
parent 7c32134fb5
commit 6564b258f1
4 changed files with 9 additions and 6 deletions

View File

@@ -62,7 +62,9 @@ class ZulipRequestNotes:
error_format: Optional[str] = None error_format: Optional[str] = None
placeholder_open_graph_description: Optional[str] = None placeholder_open_graph_description: Optional[str] = None
saved_response: Optional[HttpResponse] = None saved_response: Optional[HttpResponse] = None
tornado_handler: Optional["handlers.AsyncDjangoHandler"] = None # tornado_handler is a weak reference to work around a memory leak
# in WeakKeyDictionary (https://bugs.python.org/issue44680).
tornado_handler: Optional["weakref.ReferenceType[handlers.AsyncDjangoHandler]"] = None
processed_parameters: Set[str] = field(default_factory=set) processed_parameters: Set[str] = field(default_factory=set)
ignored_parameters: Set[str] = field(default_factory=set) ignored_parameters: Set[str] = field(default_factory=set)

View File

@@ -3,6 +3,7 @@ import os
import re import re
import sys import sys
import time import time
import weakref
from contextlib import contextmanager from contextlib import contextmanager
from functools import wraps from functools import wraps
from typing import ( from typing import (
@@ -310,7 +311,6 @@ class HostRequestMock(HttpRequest):
self.POST[key] = str(post_data[key]) self.POST[key] = str(post_data[key])
self.method = "POST" self.method = "POST"
self._tornado_handler = DummyHandler()
self._log_data: Dict[str, Any] = {} self._log_data: Dict[str, Any] = {}
if meta_data is None: if meta_data is None:
self.META = {"PATH_INFO": "test"} self.META = {"PATH_INFO": "test"}
@@ -324,7 +324,7 @@ class HostRequestMock(HttpRequest):
request_notes_map[self] = ZulipRequestNotes( request_notes_map[self] = ZulipRequestNotes(
client_name="", client_name="",
log_data={}, log_data={},
tornado_handler=tornado_handler, tornado_handler=None if tornado_handler is None else weakref.ref(tornado_handler),
client=get_client(client_name) if client_name is not None else None, client=get_client(client_name) if client_name is not None else None,
) )

View File

@@ -1,5 +1,6 @@
import logging import logging
import urllib import urllib
import weakref
from typing import Any, Dict, List from typing import Any, Dict, List
import tornado.web import tornado.web
@@ -116,7 +117,7 @@ class AsyncDjangoHandler(tornado.web.RequestHandler, base.BaseHandler):
# Provide a way for application code to access this handler # Provide a way for application code to access this handler
# given the HttpRequest object. # given the HttpRequest object.
get_request_notes(request).tornado_handler = self get_request_notes(request).tornado_handler = weakref.ref(self)
return request return request

View File

@@ -19,7 +19,6 @@ from zerver.lib.validator import (
from zerver.models import Client, UserProfile, get_client, get_user_profile_by_id from zerver.models import Client, UserProfile, get_client, get_user_profile_by_id
from zerver.tornado.event_queue import fetch_events, get_client_descriptor, process_notification from zerver.tornado.event_queue import fetch_events, get_client_descriptor, process_notification
from zerver.tornado.exceptions import BadEventQueueIdError from zerver.tornado.exceptions import BadEventQueueIdError
from zerver.tornado.handlers import AsyncDjangoHandler
@internal_notify_view(True) @internal_notify_view(True)
@@ -111,7 +110,8 @@ def get_events_backend(
# Extract the Tornado handler from the request # Extract the Tornado handler from the request
tornado_handler = get_request_notes(request).tornado_handler tornado_handler = get_request_notes(request).tornado_handler
assert tornado_handler is not None assert tornado_handler is not None
handler: AsyncDjangoHandler = tornado_handler handler = tornado_handler()
assert handler is not None
if user_client is None: if user_client is None:
valid_user_client = get_request_notes(request).client valid_user_client = get_request_notes(request).client