diff --git a/synapse/api/errors.py b/synapse/api/errors.py
index 7ffd72c42c..fdb2955be8 100644
--- a/synapse/api/errors.py
+++ b/synapse/api/errors.py
@@ -16,6 +16,7 @@
"""Contains exceptions and error codes."""
import logging
+import math
import typing
from enum import Enum
from http import HTTPStatus
@@ -210,6 +211,11 @@ class SynapseError(CodeMessageException):
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
return cs_error(self.msg, self.errcode, **self._additional_fields)
+ @property
+ def debug_context(self) -> Optional[str]:
+ """Override this to add debugging context that shouldn't be sent to clients."""
+ return None
+
class InvalidAPICallError(SynapseError):
"""You called an existing API endpoint, but fed that endpoint
@@ -503,19 +509,31 @@ class InvalidCaptchaError(SynapseError):
class LimitExceededError(SynapseError):
"""A client has sent too many requests and is being throttled."""
+ include_retry_after_header = False
+
def __init__(
self,
+ limiter_name: str,
code: int = 429,
- msg: str = "Too Many Requests",
retry_after_ms: Optional[int] = None,
errcode: str = Codes.LIMIT_EXCEEDED,
):
- super().__init__(code, msg, errcode)
+ headers = (
+ {"Retry-After": str(math.ceil(retry_after_ms / 1000))}
+ if self.include_retry_after_header and retry_after_ms is not None
+ else None
+ )
+ super().__init__(code, "Too Many Requests", errcode, headers=headers)
self.retry_after_ms = retry_after_ms
+ self.limiter_name = limiter_name
def error_dict(self, config: Optional["HomeServerConfig"]) -> "JsonDict":
return cs_error(self.msg, self.errcode, retry_after_ms=self.retry_after_ms)
+ @property
+ def debug_context(self) -> Optional[str]:
+ return self.limiter_name
+
class RoomKeysVersionError(SynapseError):
"""A client has tried to upload to a non-current version of the room_keys store"""
diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py
index 511790c7c5..02ae45e8b3 100644
--- a/synapse/api/ratelimiting.py
+++ b/synapse/api/ratelimiting.py
@@ -40,7 +40,7 @@ class Ratelimiter:
- the cost C of this request in tokens.
Then, if there is room in the bucket for C tokens (T + C <= `burst_count`),
the request is permitted and `cost` tokens are added to the bucket.
- Otherwise the request is denied, and the bucket continues to hold T tokens.
+ Otherwise, the request is denied, and the bucket continues to hold T tokens.
This means that the limiter enforces an average request frequency of `rate_hz`,
while accumulating a buffer of up to `burst_count` requests which can be consumed
@@ -55,18 +55,23 @@ class Ratelimiter:
request.
Args:
+ store: The datastore providing get_ratelimit_for_user.
clock: A homeserver clock, for retrieving the current time
- rate_hz: The long term number of actions that can be performed in a second.
- burst_count: How many actions that can be performed before being limited.
+ cfg: The ratelimit configuration for this rate limiter including the
+ allowed rate and burst count.
"""
def __init__(
- self, store: DataStore, clock: Clock, rate_hz: float, burst_count: int
+ self,
+ store: DataStore,
+ clock: Clock,
+ cfg: RatelimitSettings,
):
self.clock = clock
- self.rate_hz = rate_hz
- self.burst_count = burst_count
+ self.rate_hz = cfg.per_second
+ self.burst_count = cfg.burst_count
self.store = store
+ self._limiter_name = cfg.key
# An ordered dictionary representing the token buckets tracked by this rate
# limiter. Each entry maps a key of arbitrary type to a tuple representing:
@@ -305,7 +310,8 @@ class Ratelimiter:
if not allowed:
raise LimitExceededError(
- retry_after_ms=int(1000 * (time_allowed - time_now_s))
+ limiter_name=self._limiter_name,
+ retry_after_ms=int(1000 * (time_allowed - time_now_s)),
)
@@ -322,7 +328,9 @@ class RequestRatelimiter:
# The rate_hz and burst_count are overridden on a per-user basis
self.request_ratelimiter = Ratelimiter(
- store=self.store, clock=self.clock, rate_hz=0, burst_count=0
+ store=self.store,
+ clock=self.clock,
+ cfg=RatelimitSettings(key=rc_message.key, per_second=0, burst_count=0),
)
self._rc_message = rc_message
@@ -332,8 +340,7 @@ class RequestRatelimiter:
self.admin_redaction_ratelimiter: Optional[Ratelimiter] = Ratelimiter(
store=self.store,
clock=self.clock,
- rate_hz=rc_admin_redaction.per_second,
- burst_count=rc_admin_redaction.burst_count,
+ cfg=rc_admin_redaction,
)
else:
self.admin_redaction_ratelimiter = None
|