Add ability to cancel disconnected requests to `SynapseRequest` (#12588)
Signed-off-by: Sean Quah <seanq@element.io>
2 files changed, 24 insertions, 1 deletions
diff --git a/changelog.d/12588.misc b/changelog.d/12588.misc
new file mode 100644
index 0000000000..f62d5c8e21
--- /dev/null
+++ b/changelog.d/12588.misc
@@ -0,0 +1 @@
+Add ability to cancel disconnected requests to `SynapseRequest`.
diff --git a/synapse/http/site.py b/synapse/http/site.py
index 0b85a57d77..f7f1c57042 100644
--- a/synapse/http/site.py
+++ b/synapse/http/site.py
@@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Any, Generator, Optional, Tuple, Union
import attr
from zope.interface import implementer
+from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IAddress, IReactorTime
from twisted.python.failure import Failure
from twisted.web.http import HTTPChannel
@@ -91,6 +92,13 @@ 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`.
+ 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`.
+ self.is_render_cancellable = False
+
global _next_request_seq
self.request_seq = _next_request_seq
_next_request_seq += 1
@@ -357,7 +365,21 @@ class SynapseRequest(Request):
{"event": "client connection lost", "reason": str(reason.value)}
)
- if not self._is_processing:
+ if self._is_processing:
+ if self.is_render_cancellable:
+ if self.render_deferred is not None:
+ # Throw a cancellation into the request processing, in the hope
+ # that it will finish up sooner than it normally would.
+ # The `self.processing()` context manager will call
+ # `_finished_processing()` when done.
+ with PreserveLoggingContext():
+ self.render_deferred.cancel()
+ else:
+ logger.error(
+ "Connection from client lost, but have no Deferred to "
+ "cancel even though the request is marked as cancellable."
+ )
+ else:
self._finished_processing()
def _started_processing(self, servlet_name: str) -> None:
|