From ccc82976dc63a736870ea80e4dcfd510db3d1972 Mon Sep 17 00:00:00 2001 From: Prakhar Pratyush Date: Tue, 22 Apr 2025 14:13:23 +0530 Subject: [PATCH] unread_data: Ensure deterministic ordering of unread message rows. Earlier, in `get_raw_unread_data` the ordering was applied inside the CTE. Once we leave the CTE scope and do a join, SQL makes no promise about preserving the row order unless we re-specify ORDER BY in the outer query. Since, there was no ORDER BY clause in the outer query it was resulting in a random ordering of the entries. This bug was caught by `test_unreads_case_insensitive_topics` failing in a flaky way. This commit fixes the bug. --- zerver/lib/message.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zerver/lib/message.py b/zerver/lib/message.py index e80af28c0f..01f18aa1d1 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -857,6 +857,7 @@ def get_raw_unread_data( "flags", "recipient_id", ) + # Descending order, so truncation keeps the latest unreads. .order_by("-message_id") ) @@ -909,9 +910,13 @@ def get_raw_unread_data( "recipient__type", "recipient__type_id", ) + # Output in ascending order. We can't just reverse, + # since the CTE join does not guarantee that it + # preserves the original descending order. + .order_by("message_id") ) - rows = list(reversed(user_msgs)) + rows = list(user_msgs) finally: cursor.execute("SET enable_bitmapscan TO on") return extract_unread_data_from_um_rows(rows, user_profile)