MSC4140: update error responses#19539
MSC4140: update error responses#19539AndrewFerr wants to merge 11 commits intoelement-hq:developfrom
Conversation
Prevent users from having more than a configured number of delayed events scheduled at once.
Also don't return a custom error (that was never in the MSC) when adding a delayed event if not enabled in the config
Set the new "max_delayed_events_per_user" config in the same module
as where the other MSC4140 config ("max_event_delay_duration") is set.
Also give it aliases for experimental & stable usage.
in an attempt to better explain what it is and why it is being examined
Don't want a stable alias for it yet, as it may change
Set its value to the remaining time until the requesting user's next delayed event will be sent. Also drop test against the error message of the thrown LimitExceededError, as now the Retry-After is there to clearly distinguish the thrown error as being related to a specific delayed event request.
If delayed events are to be disabled, it should be done by leaving the max_event_delay_duration config unspecified, not by setting the delayed event limit to 0. Also add test coverage, and tweak the error thrown for max_delay_event_ms to be consistent with the newly-added error.
| # Copyright (C) 2023-2025 New Vector Ltd | ||
| # Copyright (C) 2025-2026 Element Creations Ltd |
There was a problem hiding this comment.
ehhhh, just leave it be
To be clear, https://gitlab.matrix.org/new-vector/internal/-/wikis/Copyright-headers seems to maybe align with what you're doing but I haven't seen a single other Synapse PR that does this.
Feels like something we should have a lint/tool for if we cared.
As an aside, these copyright headers are so annoying and backwards in my opinion.
There was a problem hiding this comment.
Alright, then I'll just strip the related commit from this PR.
| LimitExceededError: if the user has reached the limit of | ||
| how many delayed events they may have scheduled at once. | ||
| """ | ||
| assert limit > 0 # Should be enforced at config read time |
There was a problem hiding this comment.
Why do we enforce this? Seems valid
I see this commit message but none of that context made it into the code.
Enforce max delayed event config to be positive
If delayed events are to be disabled, it should be done by leaving the
max_event_delay_duration config unspecified, not by setting the delayed
event limit to 0.-- 43e14e9
And I don't think it's something for us to worry about at this level (just in the config validation)
| raise ConfigError( | ||
| "Expected a positive value", ("max_event_delay_duration",) | ||
| ) |
There was a problem hiding this comment.
Explain more why and what do do.
Enforce max delayed event config to be positive
If delayed events are to be disabled, it should be done by leaving the
max_event_delay_duration config unspecified, not by setting the delayed
event limit to 0.-- 43e14e9
| ) | ||
| e = LimitExceededError( | ||
| limiter_name="add_delayed_event", | ||
| retry_after_ms=next_send_ms - creation_ts, |
There was a problem hiding this comment.
next_send_ms is a timestamp? Needs a better variable name
| channel.json_body["errcode"], | ||
| channel.json_body, | ||
| ) | ||
| step_ms = 100 # The simulated duration of each make_request |
There was a problem hiding this comment.
Is this related to the 100 ms from FakeChannel.await_result? (#19487 (comment))
Needs better comment
| channel = self.make_request(*args) | ||
| self.assertEqual(HTTPStatus.TOO_MANY_REQUESTS, channel.code, channel.result) | ||
| self.assertEqual( | ||
| Codes.LIMIT_EXCEEDED, | ||
| channel.json_body["errcode"], | ||
| channel.json_body, | ||
| ) |
There was a problem hiding this comment.
Could use a sanity check on the current time to make sure it's not past the first delayed event being sent.
| expected_retry_after_ms = send_after_ms - wait_ms - step_ms | ||
| self.assertEqual( | ||
| expected_retry_after_ms, | ||
| channel.json_body["retry_after_ms"], | ||
| channel.json_body, | ||
| ) | ||
| retry_header = channel.headers.getRawHeaders("Retry-After") | ||
| assert retry_header | ||
| self.assertSequenceEqual( | ||
| [str(math.ceil(expected_retry_after_ms / 1000))], | ||
| retry_header, | ||
| ) |
There was a problem hiding this comment.
We probably don't care about testing the exact value. Just that it's > 0.
There is no comments here what we expect this to be
| retry_header = channel.headers.getRawHeaders("Retry-After") | ||
| assert retry_header |
There was a problem hiding this comment.
Feels like it could be better to assert a single header and use the single value in whatever comparison we want to do.
Otherwise, it would be better renamed retry_header and explain things.
|
|
||
| self.assertEqual(conf["listeners"], expected_listeners) | ||
|
|
||
| def test_max_delayed_events_enforces_positive(self) -> None: |
There was a problem hiding this comment.
Explain why (test doc comment)
| with self.assertRaises(ConfigError): | ||
| _read_config(generate_config(-1)) | ||
|
|
||
| def test_max_delayed_events_per_user_enforces_positive(self) -> None: |
There was a problem hiding this comment.
Explain why (test doc comment)
Pull Request Checklist
EventStoretoEventWorkerStore.".code blocks.