test_retryutils.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. # Copyright 2019 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 synapse.util.retryutils import NotRetryingDestination, get_retry_limiter
  15. from tests.unittest import HomeserverTestCase
  16. class RetryLimiterTestCase(HomeserverTestCase):
  17. def test_new_destination(self) -> None:
  18. """A happy-path case with a new destination and a successful operation"""
  19. store = self.hs.get_datastores().main
  20. limiter = self.get_success(get_retry_limiter("test_dest", self.clock, store))
  21. # advance the clock a bit before making the request
  22. self.pump(1)
  23. with limiter:
  24. pass
  25. new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
  26. self.assertIsNone(new_timings)
  27. def test_limiter(self) -> None:
  28. """General test case which walks through the process of a failing request"""
  29. store = self.hs.get_datastores().main
  30. limiter = self.get_success(get_retry_limiter("test_dest", self.clock, store))
  31. min_retry_interval_ms = (
  32. self.hs.config.federation.destination_min_retry_interval_ms
  33. )
  34. retry_multiplier = self.hs.config.federation.destination_retry_multiplier
  35. self.pump(1)
  36. try:
  37. with limiter:
  38. self.pump(1)
  39. failure_ts = self.clock.time_msec()
  40. raise AssertionError("argh")
  41. except AssertionError:
  42. pass
  43. self.pump()
  44. new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
  45. assert new_timings is not None
  46. self.assertEqual(new_timings.failure_ts, failure_ts)
  47. self.assertEqual(new_timings.retry_last_ts, failure_ts)
  48. self.assertEqual(new_timings.retry_interval, min_retry_interval_ms)
  49. # now if we try again we should get a failure
  50. self.get_failure(
  51. get_retry_limiter("test_dest", self.clock, store), NotRetryingDestination
  52. )
  53. #
  54. # advance the clock and try again
  55. #
  56. self.pump(min_retry_interval_ms)
  57. limiter = self.get_success(get_retry_limiter("test_dest", self.clock, store))
  58. self.pump(1)
  59. try:
  60. with limiter:
  61. self.pump(1)
  62. retry_ts = self.clock.time_msec()
  63. raise AssertionError("argh")
  64. except AssertionError:
  65. pass
  66. self.pump()
  67. new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
  68. assert new_timings is not None
  69. self.assertEqual(new_timings.failure_ts, failure_ts)
  70. self.assertEqual(new_timings.retry_last_ts, retry_ts)
  71. self.assertGreaterEqual(
  72. new_timings.retry_interval, min_retry_interval_ms * retry_multiplier * 0.5
  73. )
  74. self.assertLessEqual(
  75. new_timings.retry_interval, min_retry_interval_ms * retry_multiplier * 2.0
  76. )
  77. #
  78. # one more go, with success
  79. #
  80. self.reactor.advance(min_retry_interval_ms * retry_multiplier * 2.0)
  81. limiter = self.get_success(get_retry_limiter("test_dest", self.clock, store))
  82. self.pump(1)
  83. with limiter:
  84. self.pump(1)
  85. # wait for the update to land
  86. self.pump()
  87. new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
  88. self.assertIsNone(new_timings)
  89. def test_max_retry_interval(self) -> None:
  90. """Test that `destination_max_retry_interval` setting works as expected"""
  91. store = self.hs.get_datastores().main
  92. destination_max_retry_interval_ms = (
  93. self.hs.config.federation.destination_max_retry_interval_ms
  94. )
  95. self.get_success(get_retry_limiter("test_dest", self.clock, store))
  96. self.pump(1)
  97. failure_ts = self.clock.time_msec()
  98. # Simulate reaching destination_max_retry_interval
  99. self.get_success(
  100. store.set_destination_retry_timings(
  101. "test_dest",
  102. failure_ts=failure_ts,
  103. retry_last_ts=failure_ts,
  104. retry_interval=destination_max_retry_interval_ms,
  105. )
  106. )
  107. # Check it fails
  108. self.get_failure(
  109. get_retry_limiter("test_dest", self.clock, store), NotRetryingDestination
  110. )
  111. # Get past retry_interval and we can try again, and still throw an error to continue the backoff
  112. self.reactor.advance(destination_max_retry_interval_ms / 1000 + 1)
  113. limiter = self.get_success(get_retry_limiter("test_dest", self.clock, store))
  114. self.pump(1)
  115. try:
  116. with limiter:
  117. self.pump(1)
  118. raise AssertionError("argh")
  119. except AssertionError:
  120. pass
  121. self.pump()
  122. # retry_interval does not increase and stays at destination_max_retry_interval_ms
  123. new_timings = self.get_success(store.get_destination_retry_timings("test_dest"))
  124. assert new_timings is not None
  125. self.assertEqual(new_timings.retry_interval, destination_max_retry_interval_ms)
  126. # Check it fails
  127. self.get_failure(
  128. get_retry_limiter("test_dest", self.clock, store), NotRetryingDestination
  129. )