Repository: matrix-org/synapse

Issue:
Unread count keeps showing up for some rooms
### Description

A private room I’m in keeps showing a (2) unread message count on client launch, both on Element Web and Android, even after I cleared cache.

### Steps to reproduce

- got some messages in a specific private room
- read them on Element Web (the count disappears)
- left home, started Element Android, the count appeared again for that room but no new message was visible; opening the room here makes the count go away again
- went back home, started Element Web again, the same count was back again.
- read the room again, did a clear cache & reload, the count is back again.

This all happened within the past 24 hours. During that time, I also have had counts appear on direct conversations with no new events while the Web client was running. That is not as reproducible though.

### Homeserver

matrix.org

### Synapse Version

1.61.0

### Installation Method

_No response_

### Platform

:shrug: matrix.org

### Relevant log output

```shell
I don’t have access to that.
```


### Anything else that would be useful to know?

_No response_


Discussion:
> got some messages in a specific private room

Are you able to share the room ID for debugging?
!zMjRxBhQzSpwVQMPLS:gnome.org
I'm getting this issue annoyingly often for `!TxHCEvTmqctXITtVQO:matrix.org` last night and this morning

I would like to challenge the S-Minor label as the product is severely impaired if read rooms keep telling you that there are new messages in there wanting attention.
Aha! I think I've spotted the problem. We don't correctly take into account the receipts at:

https://github.com/matrix-org/synapse/blob/a962c5a56de69c03848646f25991fabe6e4c39d1/synapse/storage/databases/main/event_push_actions.py#L1013-L1026

which is usually fine as we will have deleted push actions that have been read, *except* for highlights. Which is why we see the problem in DMs
The room I was reporting this for is not a DM **but** the latest message does indeed contain a highlight to me. It has also been edited, hence a (2) count. It checks out. :slightly_smiling_face: 

Resolving patch:
diff --git a/synapse/storage/databases/main/event_push_actions.py b/synapse/storage/databases/main/event_push_actions.py
--- a/synapse/storage/databases/main/event_push_actions.py
+++ b/synapse/storage/databases/main/event_push_actions.py
@@ -1016,9 +1016,14 @@ def _rotate_notifs_before_txn(
                 upd.stream_ordering
             FROM (
                 SELECT user_id, room_id, count(*) as cnt,
-                    max(stream_ordering) as stream_ordering
-                FROM event_push_actions
-                WHERE ? < stream_ordering AND stream_ordering <= ?
+                    max(ea.stream_ordering) as stream_ordering
+                FROM event_push_actions AS ea
+                LEFT JOIN event_push_summary AS old USING (user_id, room_id)
+                WHERE ? < ea.stream_ordering AND ea.stream_ordering <= ?
+                    AND (
+                        old.last_receipt_stream_ordering IS NULL
+                        OR old.last_receipt_stream_ordering < ea.stream_ordering
+                    )
                     AND %s = 1
                 GROUP BY user_id, room_id
             ) AS upd


Test patch:
diff --git a/tests/storage/test_event_push_actions.py b/tests/storage/test_event_push_actions.py
--- a/tests/storage/test_event_push_actions.py
+++ b/tests/storage/test_event_push_actions.py
@@ -196,6 +196,13 @@ def _mark_read(stream: int, depth: int) -> None:
         _mark_read(10, 10)
         _assert_counts(0, 0)
 
+        _inject_actions(11, HIGHLIGHT)
+        _assert_counts(1, 1)
+        _mark_read(11, 11)
+        _assert_counts(0, 0)
+        _rotate(11)
+        _assert_counts(0, 0)
+
     def test_find_first_stream_ordering_after_ts(self) -> None:
         def add_event(so: int, ts: int) -> None:
             self.get_success(