Skip to content

MSC4140: update error responses#19539

Open
AndrewFerr wants to merge 11 commits intoelement-hq:developfrom
AndrewFerr:msc4140-error-updates
Open

MSC4140: update error responses#19539
AndrewFerr wants to merge 11 commits intoelement-hq:developfrom
AndrewFerr:msc4140-error-updates

Conversation

@AndrewFerr
Copy link
Member

  • Impose limit of scheduled delayed events
  • Update error codes to match latest draft of the MSC

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct (run the linters)

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
@AndrewFerr AndrewFerr requested a review from a team as a code owner March 9, 2026 20:45
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.
Comment on lines +5 to +6
# Copyright (C) 2023-2025 New Vector Ltd
# Copyright (C) 2025-2026 Element Creations Ltd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment on lines +918 to +920
raise ConfigError(
"Expected a positive value", ("max_event_delay_duration",)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to the 100 ms from FakeChannel.await_result? (#19487 (comment))

Needs better comment

Comment on lines +2488 to +2494
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,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a sanity check on the current time to make sure it's not past the first delayed event being sent.

Comment on lines +2496 to +2507
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,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +2502 to +2503
retry_header = channel.headers.getRawHeaders("Retry-After")
assert retry_header
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain why (test doc comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants