|
|
@@ -233,14 +233,30 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, EventsWorkerStore, SQLBas |
|
|
|
|
|
|
|
counts = NotifCounts() |
|
|
|
|
|
|
|
# First we pull the counts from the summary table |
|
|
|
# First we pull the counts from the summary table. |
|
|
|
# |
|
|
|
# We check that `last_receipt_stream_ordering` matches the stream |
|
|
|
# ordering given. If it doesn't match then a new read receipt has arrived and |
|
|
|
# we haven't yet updated the counts in `event_push_summary` to reflect |
|
|
|
# that; in that case we simply ignore `event_push_summary` counts |
|
|
|
# and do a manual count of all of the rows in the `event_push_actions` table |
|
|
|
# for this user/room. |
|
|
|
# |
|
|
|
# If `last_receipt_stream_ordering` is null then that means it's up to |
|
|
|
# date (as the row was written by an older version of Synapse that |
|
|
|
# updated `event_push_summary` synchronously when persisting a new read |
|
|
|
# receipt). |
|
|
|
txn.execute( |
|
|
|
""" |
|
|
|
SELECT stream_ordering, notif_count, COALESCE(unread_count, 0) |
|
|
|
FROM event_push_summary |
|
|
|
WHERE room_id = ? AND user_id = ? AND stream_ordering > ? |
|
|
|
WHERE room_id = ? AND user_id = ? |
|
|
|
AND ( |
|
|
|
(last_receipt_stream_ordering IS NULL AND stream_ordering > ?) |
|
|
|
OR last_receipt_stream_ordering = ? |
|
|
|
) |
|
|
|
""", |
|
|
|
(room_id, user_id, stream_ordering), |
|
|
|
(room_id, user_id, stream_ordering, stream_ordering), |
|
|
|
) |
|
|
|
row = txn.fetchone() |
|
|
|
|
|
|
@@ -263,9 +279,9 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, EventsWorkerStore, SQLBas |
|
|
|
if row: |
|
|
|
counts.highlight_count += row[0] |
|
|
|
|
|
|
|
# Finally we need to count push actions that haven't been summarized |
|
|
|
# yet. |
|
|
|
# We only want to pull out push actions that we haven't summarized yet. |
|
|
|
# Finally we need to count push actions that aren't included in the |
|
|
|
# summary returned above, e.g. recent events that haven't been |
|
|
|
# summarized yet, or the summary is empty due to a recent read receipt. |
|
|
|
stream_ordering = max(stream_ordering, summary_stream_ordering) |
|
|
|
notify_count, unread_count = self._get_notif_unread_count_for_user_room( |
|
|
|
txn, room_id, user_id, stream_ordering |
|
|
@@ -800,6 +816,19 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, EventsWorkerStore, SQLBas |
|
|
|
self._doing_notif_rotation = True |
|
|
|
|
|
|
|
try: |
|
|
|
# First we recalculate push summaries and delete stale push actions |
|
|
|
# for rooms/users with new receipts. |
|
|
|
while True: |
|
|
|
logger.debug("Handling new receipts") |
|
|
|
|
|
|
|
caught_up = await self.db_pool.runInteraction( |
|
|
|
"_handle_new_receipts_for_notifs_txn", |
|
|
|
self._handle_new_receipts_for_notifs_txn, |
|
|
|
) |
|
|
|
if caught_up: |
|
|
|
break |
|
|
|
|
|
|
|
# Then we update the event push summaries for any new events |
|
|
|
while True: |
|
|
|
logger.info("Rotating notifications") |
|
|
|
|
|
|
@@ -810,10 +839,110 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, EventsWorkerStore, SQLBas |
|
|
|
break |
|
|
|
await self.hs.get_clock().sleep(self._rotate_delay) |
|
|
|
|
|
|
|
# Finally we clear out old event push actions. |
|
|
|
await self._remove_old_push_actions_that_have_rotated() |
|
|
|
finally: |
|
|
|
self._doing_notif_rotation = False |
|
|
|
|
|
|
|
def _handle_new_receipts_for_notifs_txn(self, txn: LoggingTransaction) -> bool: |
|
|
|
"""Check for new read receipts and delete from event push actions. |
|
|
|
|
|
|
|
Any push actions which predate the user's most recent read receipt are |
|
|
|
now redundant, so we can remove them from `event_push_actions` and |
|
|
|
update `event_push_summary`. |
|
|
|
""" |
|
|
|
|
|
|
|
limit = 100 |
|
|
|
|
|
|
|
min_stream_id = self.db_pool.simple_select_one_onecol_txn( |
|
|
|
txn, |
|
|
|
table="event_push_summary_last_receipt_stream_id", |
|
|
|
keyvalues={}, |
|
|
|
retcol="stream_id", |
|
|
|
) |
|
|
|
|
|
|
|
sql = """ |
|
|
|
SELECT r.stream_id, r.room_id, r.user_id, e.stream_ordering |
|
|
|
FROM receipts_linearized AS r |
|
|
|
INNER JOIN events AS e USING (event_id) |
|
|
|
WHERE r.stream_id > ? AND user_id LIKE ? |
|
|
|
ORDER BY r.stream_id ASC |
|
|
|
LIMIT ? |
|
|
|
""" |
|
|
|
|
|
|
|
# We only want local users, so we add a dodgy filter to the above query |
|
|
|
# and recheck it below. |
|
|
|
user_filter = "%:" + self.hs.hostname |
|
|
|
|
|
|
|
txn.execute( |
|
|
|
sql, |
|
|
|
( |
|
|
|
min_stream_id, |
|
|
|
user_filter, |
|
|
|
limit, |
|
|
|
), |
|
|
|
) |
|
|
|
rows = txn.fetchall() |
|
|
|
|
|
|
|
# For each new read receipt we delete push actions from before it and |
|
|
|
# recalculate the summary. |
|
|
|
for _, room_id, user_id, stream_ordering in rows: |
|
|
|
# Only handle our own read receipts. |
|
|
|
if not self.hs.is_mine_id(user_id): |
|
|
|
continue |
|
|
|
|
|
|
|
txn.execute( |
|
|
|
""" |
|
|
|
DELETE FROM event_push_actions |
|
|
|
WHERE room_id = ? |
|
|
|
AND user_id = ? |
|
|
|
AND stream_ordering <= ? |
|
|
|
AND highlight = 0 |
|
|
|
""", |
|
|
|
(room_id, user_id, stream_ordering), |
|
|
|
) |
|
|
|
|
|
|
|
old_rotate_stream_ordering = self.db_pool.simple_select_one_onecol_txn( |
|
|
|
txn, |
|
|
|
table="event_push_summary_stream_ordering", |
|
|
|
keyvalues={}, |
|
|
|
retcol="stream_ordering", |
|
|
|
) |
|
|
|
|
|
|
|
notif_count, unread_count = self._get_notif_unread_count_for_user_room( |
|
|
|
txn, room_id, user_id, stream_ordering, old_rotate_stream_ordering |
|
|
|
) |
|
|
|
|
|
|
|
self.db_pool.simple_upsert_txn( |
|
|
|
txn, |
|
|
|
table="event_push_summary", |
|
|
|
keyvalues={"room_id": room_id, "user_id": user_id}, |
|
|
|
values={ |
|
|
|
"notif_count": notif_count, |
|
|
|
"unread_count": unread_count, |
|
|
|
"stream_ordering": old_rotate_stream_ordering, |
|
|
|
"last_receipt_stream_ordering": stream_ordering, |
|
|
|
}, |
|
|
|
) |
|
|
|
|
|
|
|
# We always update `event_push_summary_last_receipt_stream_id` to |
|
|
|
# ensure that we don't rescan the same receipts for remote users. |
|
|
|
# |
|
|
|
# This requires repeatable read to be safe, as we need the |
|
|
|
# `MAX(stream_id)` to not include any new rows that have been committed |
|
|
|
# since the start of the transaction (since those rows won't have been |
|
|
|
# returned by the query above). Alternatively we could query the max |
|
|
|
# stream ID at the start of the transaction and bound everything by |
|
|
|
# that. |
|
|
|
txn.execute( |
|
|
|
""" |
|
|
|
UPDATE event_push_summary_last_receipt_stream_id |
|
|
|
SET stream_id = (SELECT COALESCE(MAX(stream_id), 0) FROM receipts_linearized) |
|
|
|
""" |
|
|
|
) |
|
|
|
|
|
|
|
return len(rows) < limit |
|
|
|
|
|
|
|
def _rotate_notifs_txn(self, txn: LoggingTransaction) -> bool: |
|
|
|
"""Archives older notifications into event_push_summary. Returns whether |
|
|
|
the archiving process has caught up or not. |
|
|
@@ -1033,66 +1162,6 @@ class EventPushActionsWorkerStore(ReceiptsWorkerStore, EventsWorkerStore, SQLBas |
|
|
|
if done: |
|
|
|
break |
|
|
|
|
|
|
|
def _remove_old_push_actions_before_txn( |
|
|
|
self, txn: LoggingTransaction, room_id: str, user_id: str, stream_ordering: int |
|
|
|
) -> None: |
|
|
|
""" |
|
|
|
Purges old push actions for a user and room before a given |
|
|
|
stream_ordering. |
|
|
|
|
|
|
|
We however keep a months worth of highlighted notifications, so that |
|
|
|
users can still get a list of recent highlights. |
|
|
|
|
|
|
|
Args: |
|
|
|
txn: The transaction |
|
|
|
room_id: Room ID to delete from |
|
|
|
user_id: user ID to delete for |
|
|
|
stream_ordering: The lowest stream ordering which will |
|
|
|
not be deleted. |
|
|
|
""" |
|
|
|
txn.call_after( |
|
|
|
self.get_unread_event_push_actions_by_room_for_user.invalidate, |
|
|
|
(room_id, user_id), |
|
|
|
) |
|
|
|
|
|
|
|
# We need to join on the events table to get the received_ts for |
|
|
|
# event_push_actions and sqlite won't let us use a join in a delete so |
|
|
|
# we can't just delete where received_ts < x. Furthermore we can |
|
|
|
# only identify event_push_actions by a tuple of room_id, event_id |
|
|
|
# we we can't use a subquery. |
|
|
|
# Instead, we look up the stream ordering for the last event in that |
|
|
|
# room received before the threshold time and delete event_push_actions |
|
|
|
# in the room with a stream_odering before that. |
|
|
|
txn.execute( |
|
|
|
"DELETE FROM event_push_actions " |
|
|
|
" WHERE user_id = ? AND room_id = ? AND " |
|
|
|
" stream_ordering <= ?" |
|
|
|
" AND ((stream_ordering < ? AND highlight = 1) or highlight = 0)", |
|
|
|
(user_id, room_id, stream_ordering, self.stream_ordering_month_ago), |
|
|
|
) |
|
|
|
|
|
|
|
old_rotate_stream_ordering = self.db_pool.simple_select_one_onecol_txn( |
|
|
|
txn, |
|
|
|
table="event_push_summary_stream_ordering", |
|
|
|
keyvalues={}, |
|
|
|
retcol="stream_ordering", |
|
|
|
) |
|
|
|
|
|
|
|
notif_count, unread_count = self._get_notif_unread_count_for_user_room( |
|
|
|
txn, room_id, user_id, stream_ordering, old_rotate_stream_ordering |
|
|
|
) |
|
|
|
|
|
|
|
self.db_pool.simple_upsert_txn( |
|
|
|
txn, |
|
|
|
table="event_push_summary", |
|
|
|
keyvalues={"room_id": room_id, "user_id": user_id}, |
|
|
|
values={ |
|
|
|
"notif_count": notif_count, |
|
|
|
"unread_count": unread_count, |
|
|
|
"stream_ordering": old_rotate_stream_ordering, |
|
|
|
}, |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
class EventPushActionsStore(EventPushActionsWorkerStore): |
|
|
|
EPA_HIGHLIGHT_INDEX = "epa_highlight_index" |
|
|
|