-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add @cancellable decorator, for use on endpoint methods that can be cancelled when clients disconnect
#12583
Conversation
5fb9b3f to
7d3c2d3
Compare
Signed-off-by: Sean Quah <seanq@element.io>
Don't log stack traces for cancelled requests and use a custom HTTP status code of 499. Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
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>
`DirectServeHtmlResource` and `DirectServeJsonResource` both inherit from `_AsyncResource`. These classes expect to be subclassed with `_async_render_*` methods. This commit has no effect on `JsonResource`, despite inheriting from `_AsyncResource`. `JsonResource` has its own `_async_render` override which will need to be updated separately. Signed-off-by: Sean Quah <seanq@element.io>
…nServlet`s Both `RestServlet`s and `BaseFederationServlet`s register their handlers with `HttpServer.register_paths` / `JsonResource.register_paths`. Update `JsonResource` to respect the `@cancellable` flag on handlers registered in this way. Although `ReplicationEndpoint` also registers itself using `register_paths`, it does not pass the handler method that would have the `@cancellable` flag directly, and so needs separate handling. Signed-off-by: Sean Quah <seanq@element.io>
`BaseFederationServlet` wraps its endpoints in a bunch of async code that has not been vetted for compatibility with cancellation. Fail CI if a `@cancellable` flag is applied to a federation endpoint. Signed-off-by: Sean Quah <seanq@element.io>
While `ReplicationEndpoint`s register themselves via `JsonResource`, they pass a method that calls the handler, instead of the handler itself, to `register_paths`. As a result, `JsonResource` will not correctly pick up the `@cancellable` flag and we have to apply it ourselves. Signed-off-by: Sean Quah <seanq@element.io>
In order to simulate a client disconnection in tests, we would like to call `Request.connectionLost`. Make the `Request` accessible from the `FakeChannel` returned by `make_request`. Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
…methods Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
7d3c2d3 to
a89fc72
Compare
| The callback may be marked with the `@cancellable` decorator, which will | ||
| cause request processing to be cancelled when clients disconnect early. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should add an explicit cancellable parameter to register_paths instead.
There are a few places where we call register_paths manually, with callbacks that don't match the ^on_(GET|PUT|POST|DELETE)$ pattern, eg.
synapse/synapse/rest/client/room.py
Lines 133 to 138 in 4bc8cb4
| http_server.register_paths( | |
| "GET", | |
| client_patterns(no_state_key, v1=True), | |
| self.on_GET_no_state_key, | |
| self.__class__.__name__, | |
| ) |
There we have the option of either relaxing the validation in the @cancellable decorator or adding an explicit cancellable parameter to register_paths.
The benefit of relaxing the decorator validation is that it's more obvious that on_GET_no_state_key has cancellation enabled:
@cancellable
def on_GET_no_state_key(
self, request: SynapseRequest, room_id: str, event_type: str
) -> Awaitable[Tuple[int, JsonDict]]:
return self.on_GET(request, room_id, event_type, "")
@cancellable
async def on_GET(Signed-off-by: Sean Quah <seanq@element.io>
…HelperMixin` Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
Signed-off-by: Sean Quah <seanq@element.io>
|
All reviewed and merged now. Thank you to everyone who reviewed! |
Best reviewed commit by commit.
I'm going to try to break this up into smaller PRs.
Add a
@cancellabledecorator that can be used on async HTTP endpoints.In Synapse, we have 5 ways of defining async HTTP endpoints:
RestServletsubclasses withon_$METHODmethodsBaseFederationServletsubclasses withon_$METHODmethodsReplicationEndpointsubclasses with a_handle_requestimplementation
DirectServe{Html,Json}Resourcesubclasses with_async_render_$METHODmethodsAll of these get invoked indirectly by
_AsyncResource.render, whichstarts the async processing.
DirectServeHtmlResourceandDirectServeJsonResourceinherit from_AsyncResourcedirectly, while the rest register themselves usingregister_pathson aJsonResource, which inherits from_AsyncResource.Going to split this up into smaller PRs like so: