-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Make cleaning up pushers depend on the device_id instead of the token_id #15280
Changes from 8 commits
b2df07f
4a5ed8f
2f3f6a8
4c39125
8a36827
f248672
6d9fba6
e9f1d94
789f9c9
81c3a2a
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 @@ | ||
Make the pushers rely on the `device_id` instead of the `access_token_id` for various operations. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ | |
from synapse.push import Pusher, PusherConfig, PusherConfigException | ||
from synapse.push.pusher import PusherFactory | ||
from synapse.replication.http.push import ReplicationRemovePusherRestServlet | ||
from synapse.types import JsonDict, RoomStreamToken | ||
from synapse.types import JsonDict, RoomStreamToken, StrCollection | ||
from synapse.util.async_helpers import concurrently_execute | ||
from synapse.util.threepids import canonicalise_email | ||
|
||
|
@@ -97,7 +97,6 @@ def start(self) -> None: | |
async def add_or_update_pusher( | ||
self, | ||
user_id: str, | ||
access_token: Optional[int], | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
kind: str, | ||
app_id: str, | ||
app_display_name: str, | ||
|
@@ -128,6 +127,22 @@ async def add_or_update_pusher( | |
# stream ordering, so it will process pushes from this point onwards. | ||
last_stream_ordering = self.store.get_room_max_stream_ordering() | ||
|
||
# Before we actually persist the pusher, we check if the user already has one | ||
# for this app ID and pushkey. If so, we want to keep the access token and | ||
# device ID in place, since this could be one device modifying | ||
# (e.g. enabling/disabling) another device's pusher. | ||
# XXX(quenting): Even though we're not persisting the access_token_id for new | ||
# pushers anymore, we still need to copy existing access_token_ids over when | ||
# updating a pusher, in case the "set_device_id_for_pushers" background update | ||
# hasn't run yet. | ||
access_token_id = None | ||
existing_config = await self._get_pusher_config_for_user_by_app_id_and_pushkey( | ||
user_id, app_id, pushkey | ||
) | ||
if existing_config: | ||
device_id = existing_config.device_id | ||
access_token_id = existing_config.access_token | ||
Comment on lines
+138
to
+144
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 think this causes a race with the background update:
Is that possible? 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 guess, but it is very unlikely, because unless it happens at the same time as the very last batch of the background update, it will be cleared right after by it. Also, this would only impact old pushers where we don't have the 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. We talked about this and it should be OK since when we add the pusher back (step 3) it actually gets a new ID. So this is an extremely unlikely event to occur. |
||
|
||
# we try to create the pusher just to validate the config: it | ||
# will then get pulled out of the database, | ||
# recreated, added and started: this means we have only one | ||
|
@@ -136,7 +151,6 @@ async def add_or_update_pusher( | |
PusherConfig( | ||
id=None, | ||
user_name=user_id, | ||
access_token=access_token, | ||
profile_tag=profile_tag, | ||
kind=kind, | ||
app_id=app_id, | ||
|
@@ -151,23 +165,12 @@ async def add_or_update_pusher( | |
failing_since=None, | ||
enabled=enabled, | ||
device_id=device_id, | ||
access_token=access_token_id, | ||
) | ||
) | ||
|
||
# Before we actually persist the pusher, we check if the user already has one | ||
# this app ID and pushkey. If so, we want to keep the access token and device ID | ||
# in place, since this could be one device modifying (e.g. enabling/disabling) | ||
# another device's pusher. | ||
existing_config = await self._get_pusher_config_for_user_by_app_id_and_pushkey( | ||
user_id, app_id, pushkey | ||
) | ||
if existing_config: | ||
access_token = existing_config.access_token | ||
device_id = existing_config.device_id | ||
|
||
await self.store.add_pusher( | ||
user_id=user_id, | ||
access_token=access_token, | ||
kind=kind, | ||
app_id=app_id, | ||
app_display_name=app_display_name, | ||
|
@@ -180,6 +183,7 @@ async def add_or_update_pusher( | |
profile_tag=profile_tag, | ||
enabled=enabled, | ||
device_id=device_id, | ||
access_token_id=access_token_id, | ||
) | ||
pusher = await self.process_pusher_change_by_id(app_id, pushkey, user_id) | ||
|
||
|
@@ -199,16 +203,18 @@ async def remove_pushers_by_app_id_and_pushkey_not_user( | |
) | ||
await self.remove_pusher(p.app_id, p.pushkey, p.user_name) | ||
|
||
async def remove_pushers_by_access_token( | ||
async def remove_pushers_by_access_tokens( | ||
self, user_id: str, access_tokens: Iterable[int] | ||
) -> None: | ||
"""Remove the pushers for a given user corresponding to a set of | ||
"""Remove the HTTP pushers for a given user corresponding to a set of | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
access_tokens. | ||
|
||
Args: | ||
user_id: user to remove pushers for | ||
access_tokens: access token *ids* to remove pushers for | ||
""" | ||
# XXX(quenting): This is only needed until the "set_device_id_for_pushers" | ||
# background update finishes | ||
tokens = set(access_tokens) | ||
for p in await self.store.get_pushers_by_user_id(user_id): | ||
if p.access_token in tokens: | ||
|
@@ -220,6 +226,26 @@ async def remove_pushers_by_access_token( | |
) | ||
await self.remove_pusher(p.app_id, p.pushkey, p.user_name) | ||
|
||
async def remove_pushers_by_devices( | ||
self, user_id: str, devices: StrCollection | ||
) -> None: | ||
"""Remove the HTTP pushers for a given user corresponding to a set of devices | ||
sandhose marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Args: | ||
user_id: user to remove pushers for | ||
devices: device IDs to remove pushers for | ||
""" | ||
device_ids = set(devices) | ||
for p in await self.store.get_pushers_by_user_id(user_id): | ||
if p.device_id in device_ids: | ||
logger.info( | ||
"Removing pusher for app id %s, pushkey %s, user %s", | ||
p.app_id, | ||
p.pushkey, | ||
p.user_name, | ||
) | ||
await self.remove_pusher(p.app_id, p.pushkey, p.user_name) | ||
|
||
def on_new_notifications(self, max_token: RoomStreamToken) -> None: | ||
if not self.pushers: | ||
# nothing to do here. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/* Copyright 2023 The Matrix.org Foundation C.I.C | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
-- Triggers the background update to set the device_id for pushers | ||
-- that don't have one, and clear the access_token column. | ||
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES | ||
(7402, 'set_device_id_for_pushers', '{}'); |
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.
Shouldn't we not provide a
device_id
here? (Otherwise, why is itOptional
?)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.
I think the answer to this is that it is optional, but should almost always be provided. It seems the only time it isn't is when a user is registered via the admin API w/ an email.