-
Notifications
You must be signed in to change notification settings - Fork 562
MSC4140: update error responses #19539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
c2d1039
c70c428
3b51b48
ac2bed9
e02f554
2e01d7f
79a0218
a614ebb
448bf17
43e14e9
f69ddc1
437a034
5dfc8b8
1b19c13
267a7e5
df09f8d
3804db0
184b32d
c19df5d
377cac4
0b0aab1
d62f440
4a9464c
79a6f31
34c5ce9
2eea7f5
87ce5f3
e1a0ba4
1a2895b
7bc51d7
56439ef
d2e84b7
af16899
24a543a
b6e61ef
2278272
d805c4d
5390153
a3d0def
45b6fc9
da30132
bcd5b61
80b47aa
06a6a9e
ba17cda
17d1bfe
2f0a302
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): Update error responses to match their format in the current draft of the MSC. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): Limit how many delayed events a user may have scheduled at once. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ | |
|
|
||
| import attr | ||
|
|
||
| from synapse.api.errors import NotFoundError | ||
| from synapse.api.errors import LimitExceededError, NotFoundError | ||
| from synapse.storage._base import SQLBaseStore, db_to_json | ||
| from synapse.storage.database import ( | ||
| DatabasePool, | ||
|
|
@@ -124,18 +124,79 @@ async def add_delayed_event( | |
| content: JsonDict, | ||
| delay: int, | ||
| sticky_duration_ms: int | None, | ||
| limit: int, | ||
| ) -> tuple[DelayID, Timestamp]: | ||
| """ | ||
| Inserts a new delayed event in the DB. | ||
|
|
||
| Args: | ||
| user_localpart: The localpart of the requester of the delayed event, who will be its owner. | ||
| device_id: The device ID of the requester. | ||
| creation_ts: The timestamp of when the request to add the delayed event was made. | ||
| room_id: The ID of the room where the event should be sent to. | ||
| event_type: The type of event to be sent. | ||
| state_key: The state key of the event to be sent, or None if it is not a state event. | ||
| origin_server_ts: The custom timestamp to send the event with. | ||
| If None, the timestamp will be the actual time when the event is sent. | ||
| content: The content of the event to be sent. | ||
| delay: How long (in milliseconds) to wait before automatically sending the event. | ||
| sticky_duration_ms: If an MSC4354 sticky event: the sticky duration (in milliseconds). | ||
| The event will be attempted to be reliably delivered to clients and remote servers | ||
| during its sticky period. | ||
| limit: The maximum number of delayed events the DB may store for the given requester. | ||
| Must be greater than 0. | ||
| Returns: The generated ID assigned to the added delayed event, | ||
| and the send time of the next delayed event to be sent, | ||
| which is either the event just added or one added earlier. | ||
|
|
||
| Raises: | ||
| LimitExceededError: if the DB has reached the limit of | ||
| how many delayed events it may store for the given requester. | ||
| ValueError: if the limit is not greater than 0. | ||
| """ | ||
| if limit <= 0: | ||
| raise ValueError("limit must be greater than 0") | ||
|
|
||
| delay_id = _generate_delay_id() | ||
| send_ts = Timestamp(creation_ts + delay) | ||
|
|
||
| def add_delayed_event_txn(txn: LoggingTransaction) -> Timestamp: | ||
| num_existing: int = self.db_pool.simple_select_one_onecol_txn( | ||
| txn, | ||
| table="delayed_events", | ||
| keyvalues={"user_localpart": user_localpart}, | ||
| retcol="COUNT(*)", | ||
| ) | ||
|
Comment on lines
+164
to
+169
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double-checked there is a database index for this ✅ : $ psql --username=postgres synapse
psql (18.3)
Type "help" for help.
synapse=# \d+ delayed_events
Table "public.delayed_events"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------------------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
delay_id | text | | not null | | extended | | |
user_localpart | text | | not null | | extended | | |
device_id | text | | | | extended | | |
delay | bigint | | not null | | plain | | |
send_ts | bigint | | not null | | plain | | |
room_id | text | | not null | | extended | | |
event_type | text | | not null | | extended | | |
state_key | text | | | | extended | | |
origin_server_ts | bigint | | | | plain | | |
content | text | | not null | | extended | | |
is_processed | boolean | | not null | false | plain | | |
sticky_duration_ms | bigint | | | | plain | | |
Indexes:
"delayed_events_pkey" PRIMARY KEY, btree (user_localpart, delay_id)
"delayed_events_idx" UNIQUE, btree (delay_id)
"delayed_events_is_processed" btree (is_processed)
"delayed_events_room_state_event_idx" btree (room_id, event_type, state_key) WHERE state_key IS NOT NULL
"delayed_events_send_ts" btree (send_ts)
Not-null constraints:
"delayed_events_delay_id_not_null" NOT NULL "delay_id"
"delayed_events_user_localpart_not_null" NOT NULL "user_localpart"
"delayed_events_delay_not_null" NOT NULL "delay"
"delayed_events_send_ts_not_null" NOT NULL "send_ts"
"delayed_events_room_id_not_null" NOT NULL "room_id"
"delayed_events_event_type_not_null" NOT NULL "event_type"
"delayed_events_content_not_null" NOT NULL "content"
"delayed_events_is_processed_not_null" NOT NULL "is_processed"
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Testing the new queries with
Comment on lines
+164
to
+169
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this query be looking for In this vein, there should probably be a test to make sure that if you sent N number delayed events (more than the configured limit) which are all in the past, you can still send more delayed events.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test added by e1a0ba4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This query wasn't updated to take into account I don't think that test covers this case especially given it didn't catch this.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The change I did make is to have both queries here ignore As a reminder, da30132 adds a test to confirm that the limit properly counts
I believe this is already done, because the two tests that set a limit of N>0 both check that an N+1th delayed event can be scheduled after having waited for as long as With that said, what I had thought you meant was to test lowering the limit after already having scheduled enough events to exceed the new limit. That's what e1a0ba4 was for. |
||
| if num_existing >= limit: | ||
| # Cover case of num_existing > limit, which can happen by reducing | ||
| # the configured limit after delayed events have already been added | ||
| # (or by calling this method with a limit lower than the configured one) | ||
| # | ||
| # FIXME: Remove "AS subquery" after dropping support for PostgreSQL <16 | ||
| txn.execute( | ||
| """ | ||
| SELECT MAX(send_ts) FROM ( | ||
| SELECT * FROM delayed_events | ||
| WHERE user_localpart = ? | ||
| ORDER BY send_ts ASC | ||
| LIMIT ? | ||
| ) AS subquery | ||
|
Comment on lines
+178
to
+183
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This deserves a proper comment about what it's trying to do. Explanation for myself: First find N (closest to being sent) existing delayed events over the configured limit. Then take the max
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+178
to
+183
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overall, I'd also be fine with the simplification where we just grab the max
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should admittedly be a rare edge case, but I still feel uneasy leaving it unhandled, especially since the extra cost of the more complex query is offset by it not being on a hot path.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wrote "max
With my simplified suggestion, we're still handling the edge case. The edge case happens when the homeserver admin reduces
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The downside of the simplified approach is that it makes the returned Granted, that situation is possible only when a homeserver admin restarts the server with a lower limit while users have enough scheduled events to exceed the new limit. Although I'd expect that situation be rare, there is nothing stopping it from happening, so it may as well be addressed. |
||
| """, | ||
| ( | ||
| user_localpart, | ||
| num_existing - limit + 1, | ||
| ), | ||
| ) | ||
| row = txn.fetchone() | ||
| assert row | ||
| retry_after_ms = int(row[0]) - creation_ts | ||
| err = LimitExceededError( | ||
| limiter_name="add_delayed_event", | ||
| retry_after_ms=retry_after_ms if retry_after_ms > 0 else None, | ||
| ) | ||
| err.msg = "The maximum number of delayed events has been reached." | ||
| raise err | ||
|
|
||
| self.db_pool.simple_insert_txn( | ||
| txn, | ||
| table="delayed_events", | ||
|
|
||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
267a7e5 rewords the error message to be a bit more descriptive, and to follow the same format used by similar errors in this module.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the original intentions, I was more looking for something like this:
It seems like ideally, we would have had this kind of structure for delayed event config:
If we're going with
0as a valid value to disable delayed events, I guess the updated error message works ⏩There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That new config does look better, though I'd prefer using a dedicated PR to change it, given that
max_event_delay_durationhas been around for a while now & moving it would be a breaking change.I'd also prefer to keep config suggestions in the documentation instead of error messages, to reduce churn on code changes while the MSC is still unstable.