Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit dffecad

Browse files
authored
Respect the @cancellable flag for DirectServe{Html,Json}Resources (#12698)
`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>
1 parent a4c7591 commit dffecad

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

changelog.d/12698.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Respect the `@cancellable` flag for `DirectServe{Html,Json}Resource`s.

synapse/http/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ async def _async_render(self, request: SynapseRequest) -> Optional[Tuple[int, An
382382

383383
method_handler = getattr(self, "_async_render_%s" % (request_method,), None)
384384
if method_handler:
385+
request.is_render_cancellable = is_method_cancellable(method_handler)
386+
385387
raw_callback_return = method_handler(request)
386388

387389
# Is it synchronous? We'll allow this for now.

tests/test_server.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,28 @@
1313
# limitations under the License.
1414

1515
import re
16+
from http import HTTPStatus
17+
from typing import Tuple
1618

1719
from twisted.internet.defer import Deferred
1820
from twisted.web.resource import Resource
1921

2022
from synapse.api.errors import Codes, RedirectException, SynapseError
2123
from synapse.config.server import parse_listener_def
22-
from synapse.http.server import DirectServeHtmlResource, JsonResource, OptionsResource
23-
from synapse.http.site import SynapseSite
24+
from synapse.http.server import (
25+
DirectServeHtmlResource,
26+
DirectServeJsonResource,
27+
JsonResource,
28+
OptionsResource,
29+
cancellable,
30+
)
31+
from synapse.http.site import SynapseRequest, SynapseSite
2432
from synapse.logging.context import make_deferred_yieldable
33+
from synapse.types import JsonDict
2534
from synapse.util import Clock
2635

2736
from tests import unittest
37+
from tests.http.server._base import EndpointCancellationTestHelperMixin
2838
from tests.server import (
2939
FakeSite,
3040
ThreadedMemoryReactorClock,
@@ -363,3 +373,100 @@ async def callback(request):
363373

364374
self.assertEqual(channel.result["code"], b"200")
365375
self.assertNotIn("body", channel.result)
376+
377+
378+
class CancellableDirectServeJsonResource(DirectServeJsonResource):
379+
def __init__(self, clock: Clock):
380+
super().__init__()
381+
self.clock = clock
382+
383+
@cancellable
384+
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
385+
await self.clock.sleep(1.0)
386+
return HTTPStatus.OK, {"result": True}
387+
388+
async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
389+
await self.clock.sleep(1.0)
390+
return HTTPStatus.OK, {"result": True}
391+
392+
393+
class CancellableDirectServeHtmlResource(DirectServeHtmlResource):
394+
ERROR_TEMPLATE = "{code} {msg}"
395+
396+
def __init__(self, clock: Clock):
397+
super().__init__()
398+
self.clock = clock
399+
400+
@cancellable
401+
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, bytes]:
402+
await self.clock.sleep(1.0)
403+
return HTTPStatus.OK, b"ok"
404+
405+
async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, bytes]:
406+
await self.clock.sleep(1.0)
407+
return HTTPStatus.OK, b"ok"
408+
409+
410+
class DirectServeJsonResourceCancellationTests(EndpointCancellationTestHelperMixin):
411+
"""Tests for `DirectServeJsonResource` cancellation."""
412+
413+
def setUp(self):
414+
self.reactor = ThreadedMemoryReactorClock()
415+
self.clock = Clock(self.reactor)
416+
self.resource = CancellableDirectServeJsonResource(self.clock)
417+
self.site = FakeSite(self.resource, self.reactor)
418+
419+
def test_cancellable_disconnect(self) -> None:
420+
"""Test that handlers with the `@cancellable` flag can be cancelled."""
421+
channel = make_request(
422+
self.reactor, self.site, "GET", "/sleep", await_result=False
423+
)
424+
self._test_disconnect(
425+
self.reactor,
426+
channel,
427+
expect_cancellation=True,
428+
expected_body={"error": "Request cancelled", "errcode": Codes.UNKNOWN},
429+
)
430+
431+
def test_uncancellable_disconnect(self) -> None:
432+
"""Test that handlers without the `@cancellable` flag cannot be cancelled."""
433+
channel = make_request(
434+
self.reactor, self.site, "POST", "/sleep", await_result=False
435+
)
436+
self._test_disconnect(
437+
self.reactor,
438+
channel,
439+
expect_cancellation=False,
440+
expected_body={"result": True},
441+
)
442+
443+
444+
class DirectServeHtmlResourceCancellationTests(EndpointCancellationTestHelperMixin):
445+
"""Tests for `DirectServeHtmlResource` cancellation."""
446+
447+
def setUp(self):
448+
self.reactor = ThreadedMemoryReactorClock()
449+
self.clock = Clock(self.reactor)
450+
self.resource = CancellableDirectServeHtmlResource(self.clock)
451+
self.site = FakeSite(self.resource, self.reactor)
452+
453+
def test_cancellable_disconnect(self) -> None:
454+
"""Test that handlers with the `@cancellable` flag can be cancelled."""
455+
channel = make_request(
456+
self.reactor, self.site, "GET", "/sleep", await_result=False
457+
)
458+
self._test_disconnect(
459+
self.reactor,
460+
channel,
461+
expect_cancellation=True,
462+
expected_body=b"499 Request cancelled",
463+
)
464+
465+
def test_uncancellable_disconnect(self) -> None:
466+
"""Test that handlers without the `@cancellable` flag cannot be cancelled."""
467+
channel = make_request(
468+
self.reactor, self.site, "POST", "/sleep", await_result=False
469+
)
470+
self._test_disconnect(
471+
self.reactor, channel, expect_cancellation=False, expected_body=b"ok"
472+
)

0 commit comments

Comments
 (0)