You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

163 lines
6.2 KiB

  1. # Copyright 2018-2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from http import HTTPStatus
  15. from typing import Any, Generator, Tuple, cast
  16. from unittest.mock import Mock, call
  17. from twisted.internet import defer, reactor as _reactor
  18. from synapse.logging.context import SENTINEL_CONTEXT, LoggingContext, current_context
  19. from synapse.rest.client.transactions import CLEANUP_PERIOD_MS, HttpTransactionCache
  20. from synapse.types import ISynapseReactor, JsonDict
  21. from synapse.util import Clock
  22. from tests import unittest
  23. from tests.test_utils import make_awaitable
  24. from tests.utils import MockClock
  25. reactor = cast(ISynapseReactor, _reactor)
  26. class HttpTransactionCacheTestCase(unittest.TestCase):
  27. def setUp(self) -> None:
  28. self.clock = MockClock()
  29. self.hs = Mock()
  30. self.hs.get_clock = Mock(return_value=self.clock)
  31. self.hs.get_auth = Mock()
  32. self.cache = HttpTransactionCache(self.hs)
  33. self.mock_http_response = (HTTPStatus.OK, {"result": "GOOD JOB!"})
  34. self.mock_key = "foo"
  35. @defer.inlineCallbacks
  36. def test_executes_given_function(
  37. self,
  38. ) -> Generator["defer.Deferred[Any]", object, None]:
  39. cb = Mock(return_value=make_awaitable(self.mock_http_response))
  40. res = yield self.cache.fetch_or_execute(
  41. self.mock_key, cb, "some_arg", keyword="arg"
  42. )
  43. cb.assert_called_once_with("some_arg", keyword="arg")
  44. self.assertEqual(res, self.mock_http_response)
  45. @defer.inlineCallbacks
  46. def test_deduplicates_based_on_key(
  47. self,
  48. ) -> Generator["defer.Deferred[Any]", object, None]:
  49. cb = Mock(return_value=make_awaitable(self.mock_http_response))
  50. for i in range(3): # invoke multiple times
  51. res = yield self.cache.fetch_or_execute(
  52. self.mock_key, cb, "some_arg", keyword="arg", changing_args=i
  53. )
  54. self.assertEqual(res, self.mock_http_response)
  55. # expect only a single call to do the work
  56. cb.assert_called_once_with("some_arg", keyword="arg", changing_args=0)
  57. @defer.inlineCallbacks
  58. def test_logcontexts_with_async_result(
  59. self,
  60. ) -> Generator["defer.Deferred[Any]", object, None]:
  61. @defer.inlineCallbacks
  62. def cb() -> Generator["defer.Deferred[object]", object, Tuple[int, JsonDict]]:
  63. yield Clock(reactor).sleep(0)
  64. return 1, {}
  65. @defer.inlineCallbacks
  66. def test() -> Generator["defer.Deferred[Any]", object, None]:
  67. with LoggingContext("c") as c1:
  68. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  69. self.assertIs(current_context(), c1)
  70. self.assertEqual(res, (1, {}))
  71. # run the test twice in parallel
  72. d = defer.gatherResults([test(), test()])
  73. self.assertIs(current_context(), SENTINEL_CONTEXT)
  74. yield d
  75. self.assertIs(current_context(), SENTINEL_CONTEXT)
  76. @defer.inlineCallbacks
  77. def test_does_not_cache_exceptions(
  78. self,
  79. ) -> Generator["defer.Deferred[Any]", object, None]:
  80. """Checks that, if the callback throws an exception, it is called again
  81. for the next request.
  82. """
  83. called = [False]
  84. def cb() -> "defer.Deferred[Tuple[int, JsonDict]]":
  85. if called[0]:
  86. # return a valid result the second time
  87. return defer.succeed(self.mock_http_response)
  88. called[0] = True
  89. raise Exception("boo")
  90. with LoggingContext("test") as test_context:
  91. try:
  92. yield self.cache.fetch_or_execute(self.mock_key, cb)
  93. except Exception as e:
  94. self.assertEqual(e.args[0], "boo")
  95. self.assertIs(current_context(), test_context)
  96. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  97. self.assertEqual(res, self.mock_http_response)
  98. self.assertIs(current_context(), test_context)
  99. @defer.inlineCallbacks
  100. def test_does_not_cache_failures(
  101. self,
  102. ) -> Generator["defer.Deferred[Any]", object, None]:
  103. """Checks that, if the callback returns a failure, it is called again
  104. for the next request.
  105. """
  106. called = [False]
  107. def cb() -> "defer.Deferred[Tuple[int, JsonDict]]":
  108. if called[0]:
  109. # return a valid result the second time
  110. return defer.succeed(self.mock_http_response)
  111. called[0] = True
  112. return defer.fail(Exception("boo"))
  113. with LoggingContext("test") as test_context:
  114. try:
  115. yield self.cache.fetch_or_execute(self.mock_key, cb)
  116. except Exception as e:
  117. self.assertEqual(e.args[0], "boo")
  118. self.assertIs(current_context(), test_context)
  119. res = yield self.cache.fetch_or_execute(self.mock_key, cb)
  120. self.assertEqual(res, self.mock_http_response)
  121. self.assertIs(current_context(), test_context)
  122. @defer.inlineCallbacks
  123. def test_cleans_up(self) -> Generator["defer.Deferred[Any]", object, None]:
  124. cb = Mock(return_value=make_awaitable(self.mock_http_response))
  125. yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg")
  126. # should NOT have cleaned up yet
  127. self.clock.advance_time_msec(CLEANUP_PERIOD_MS / 2)
  128. yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg")
  129. # still using cache
  130. cb.assert_called_once_with("an arg")
  131. self.clock.advance_time_msec(CLEANUP_PERIOD_MS)
  132. yield self.cache.fetch_or_execute(self.mock_key, cb, "an arg")
  133. # no longer using cache
  134. self.assertEqual(cb.call_count, 2)
  135. self.assertEqual(cb.call_args_list, [call("an arg"), call("an arg")])