From c997bfb926a29f0ec894fca889cc5eae603f4027 Mon Sep 17 00:00:00 2001
From: Sean Quah <8349537+squahtx@users.noreply.github.com>
Date: Tue, 10 May 2022 20:39:05 +0100
Subject: [PATCH] Capture the `Deferred` for request cancellation in
 `_AsyncResource` (#12694)

All async request processing goes through `_AsyncResource`, so this is
the only place where a `Deferred` needs to be captured for cancellation.

Unfortunately, the same isn't true for determining whether a request
can be cancelled. Each of `RestServlet`, `BaseFederationServlet`,
`DirectServe{Html,Json}Resource` and `ReplicationEndpoint` have
different wrappers around the method doing the request handling and they
all need to be handled separately.

Signed-off-by: Sean Quah <seanq@element.io>
---
 changelog.d/12694.misc | 1 +
 synapse/http/server.py | 4 +++-
 synapse/http/site.py   | 9 +++++----
 3 files changed, 9 insertions(+), 5 deletions(-)
 create mode 100644 changelog.d/12694.misc

diff --git a/changelog.d/12694.misc b/changelog.d/12694.misc
new file mode 100644
index 0000000000..e1e956a513
--- /dev/null
+++ b/changelog.d/12694.misc
@@ -0,0 +1 @@
+Capture the `Deferred` for request cancellation in `_AsyncResource`.
diff --git a/synapse/http/server.py b/synapse/http/server.py
index 8c96f2196e..4b4debc5cd 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -344,7 +344,9 @@ class _AsyncResource(resource.Resource, metaclass=abc.ABCMeta):
 
     def render(self, request: SynapseRequest) -> int:
         """This gets called by twisted every time someone sends us a request."""
-        defer.ensureDeferred(self._async_render_wrapper(request))
+        request.render_deferred = defer.ensureDeferred(
+            self._async_render_wrapper(request)
+        )
         return NOT_DONE_YET
 
     @wrap_async_request_handler
diff --git a/synapse/http/site.py b/synapse/http/site.py
index f7f1c57042..eeec74b78a 100644
--- a/synapse/http/site.py
+++ b/synapse/http/site.py
@@ -92,11 +92,12 @@ class SynapseRequest(Request):
         # we can't yet create the logcontext, as we don't know the method.
         self.logcontext: Optional[LoggingContext] = None
 
-        # The `Deferred` to cancel if the client disconnects early. Expected to be set
-        # by `Resource.render`.
+        # The `Deferred` to cancel if the client disconnects early and
+        # `is_render_cancellable` is set. Expected to be set by `Resource.render`.
         self.render_deferred: Optional["Deferred[None]"] = None
-        # A boolean indicating whether `_render_deferred` should be cancelled if the
-        # client disconnects early. Expected to be set during `Resource.render`.
+        # A boolean indicating whether `render_deferred` should be cancelled if the
+        # client disconnects early. Expected to be set by the coroutine started by
+        # `Resource.render`, if rendering is asynchronous.
         self.is_render_cancellable = False
 
         global _next_request_seq
-- 
GitLab