From bc140e1e7d8301a6fbb8134713f8a3413991948c Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 28 Apr 2026 04:37:01 +0200 Subject: Update synapse patches for 1.151 --- .../patches/0001-nix-use-postgres-17.patch | 4 +- .../patches/0002-nix-fix-flake.patch | 4 +- .../patches/0003-nix-Update-flake.patch | 4 +- .../0004-nix-Temporarily-disable-go-in-flake.patch | 4 +- .../patches/0005-Add-test-script.patch | 4 +- .../0006-Fix-gitignore-to-ignore-.venv.patch | 4 +- .../patches/0007-Fast-auth-links.patch | 4 +- ...h-logging-to-room-summary-over-federation.patch | 8 +- ...-entire-room-if-accessibility-check-fails.patch | 8 +- .../0010-Log-policy-server-rejected-events.patch | 31 --- ...-Use-parse_boolean-for-unredacted-content.patch | 30 +++ .../0011-Expose-tombstone-in-room-admin-api.patch | 115 +++++++++ ...-Use-parse_boolean-for-unredacted-content.patch | 30 --- .../0012-Expose-tombstone-in-room-admin-api.patch | 115 --------- ...recheck-messages-pagination-data-if-a-bac.patch | 196 ++++++++++++++++ ...nation-with-large-gaps-of-rejected-events.patch | 51 ++++ ...recheck-messages-pagination-data-if-a-bac.patch | 196 ---------------- ...nation-with-large-gaps-of-rejected-events.patch | 51 ---- ...4-RequestRatelimiter-expose-can_do_action.patch | 96 ++++++++ ..._event_ids-assert-in-event-creation-handl.patch | 26 +++ ...5-RequestRatelimiter-expose-can_do_action.patch | 96 -------- .../0016-Add-bulk-send-events-endpoint.patch | 195 ++++++++++++++++ ..._event_ids-assert-in-event-creation-handl.patch | 26 --- .../0017-Add-bulk-send-events-endpoint.patch | 196 ---------------- .../patches/0017-admin-api-send-more-data.patch | 170 ++++++++++++++ ...llow-overriding-max-background-task-count.patch | 52 +++++ .../patches/0018-admin-api-send-more-data.patch | 170 -------------- ...llow-overriding-max-background-task-count.patch | 52 ----- ...-were-probably-switched.-Set-max-to-arbit.patch | 39 ++++ .../matrix-synapse/patches/0020-changelog.patch | 20 ++ ...-were-probably-switched.-Set-max-to-arbit.patch | 39 ---- .../0021-Update-changelog.d-19394.bugfix.patch | 20 ++ .../matrix-synapse/patches/0021-changelog.patch | 20 -- ...the-retry-interval-actually-being-a-timeo.patch | 257 +++++++++++++++++++++ .../0022-Update-changelog.d-19394.bugfix.patch | 20 -- ...the-retry-interval-actually-being-a-timeo.patch | 257 --------------------- ...y-long-pump-in-test-left-over-from-testin.patch | 26 +++ .../patches/0024-adjust-changelog-again.patch | 19 ++ ...y-long-pump-in-test-left-over-from-testin.patch | 26 --- .../patches/0025-adjust-changelog-again.patch | 19 -- 40 files changed, 1334 insertions(+), 1366 deletions(-) delete mode 100644 packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch create mode 100644 packages/overlays/matrix-synapse/patches/0010-Use-parse_boolean-for-unredacted-content.patch create mode 100644 packages/overlays/matrix-synapse/patches/0011-Expose-tombstone-in-room-admin-api.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch create mode 100644 packages/overlays/matrix-synapse/patches/0012-fix-Always-recheck-messages-pagination-data-if-a-bac.patch create mode 100644 packages/overlays/matrix-synapse/patches/0013-Fix-pagination-with-large-gaps-of-rejected-events.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0013-fix-Always-recheck-messages-pagination-data-if-a-bac.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch create mode 100644 packages/overlays/matrix-synapse/patches/0014-RequestRatelimiter-expose-can_do_action.patch create mode 100644 packages/overlays/matrix-synapse/patches/0015-Clarify-pre_event_ids-assert-in-event-creation-handl.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch create mode 100644 packages/overlays/matrix-synapse/patches/0016-Add-bulk-send-events-endpoint.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0016-Clarify-pre_event_ids-assert-in-event-creation-handl.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch create mode 100644 packages/overlays/matrix-synapse/patches/0017-admin-api-send-more-data.patch create mode 100644 packages/overlays/matrix-synapse/patches/0018-Allow-overriding-max-background-task-count.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch create mode 100644 packages/overlays/matrix-synapse/patches/0019-max-and-min-were-probably-switched.-Set-max-to-arbit.patch create mode 100644 packages/overlays/matrix-synapse/patches/0020-changelog.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch create mode 100644 packages/overlays/matrix-synapse/patches/0021-Update-changelog.d-19394.bugfix.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0021-changelog.patch create mode 100644 packages/overlays/matrix-synapse/patches/0022-Adjust-for-the-retry-interval-actually-being-a-timeo.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch create mode 100644 packages/overlays/matrix-synapse/patches/0023-unecessarily-long-pump-in-test-left-over-from-testin.patch create mode 100644 packages/overlays/matrix-synapse/patches/0024-adjust-changelog-again.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0024-unecessarily-long-pump-in-test-left-over-from-testin.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch diff --git a/packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch b/packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch index 64e554b..accdedf 100644 --- a/packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch +++ b/packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch @@ -1,7 +1,7 @@ -From 1bcec5a1e035e7294d16f4de7cccd25de09fb40a Mon Sep 17 00:00:00 2001 +From 9279a3afa3792427a87caf864e68e631aec3b407 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 25 Jul 2025 08:25:28 +0200 -Subject: [PATCH 01/25] nix: use postgres 17 +Subject: [PATCH 01/24] nix: use postgres 17 Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch b/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch index b0258c3..89f7a7c 100644 --- a/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch +++ b/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch @@ -1,7 +1,7 @@ -From caa9828ad8335c75eb0f1699e983dd44efa66b5b Mon Sep 17 00:00:00 2001 +From 3815d07cfc961a33970ff6a633b2a784347679e6 Mon Sep 17 00:00:00 2001 From: Rory& Date: Mon, 9 Jun 2025 17:38:34 +0200 -Subject: [PATCH 02/25] nix: fix flake +Subject: [PATCH 02/24] nix: fix flake Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch b/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch index 342f16d..8c6d965 100644 --- a/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch +++ b/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch @@ -1,7 +1,7 @@ -From a858063c19500691e638d368a3f49e02889b315f Mon Sep 17 00:00:00 2001 +From 2af8ac106c6293bc4773f6ce6bc4726de34717ef Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 13 Nov 2025 13:57:10 +0100 -Subject: [PATCH 03/25] nix: Update flake +Subject: [PATCH 03/24] nix: Update flake Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch b/packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch index c814dbf..d07e7b6 100644 --- a/packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch +++ b/packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch @@ -1,7 +1,7 @@ -From a4aeac60b232762c683ff6d2bfcbe56ae2bc35ee Mon Sep 17 00:00:00 2001 +From e22d8e8148e718517a97a3862c1703e9cd8d3798 Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 16 Aug 2025 20:18:45 +0200 -Subject: [PATCH 04/25] nix: Temporarily disable go in flake +Subject: [PATCH 04/24] nix: Temporarily disable go in flake Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch b/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch index ca7d47e..298b5ae 100644 --- a/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch +++ b/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch @@ -1,7 +1,7 @@ -From 5174d4530faf93639c657e58a6a27935986da7b4 Mon Sep 17 00:00:00 2001 +From 09271771eee0188524ec1a553e8788c828846f5b Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 13 Nov 2025 13:56:59 +0100 -Subject: [PATCH 05/25] Add test script +Subject: [PATCH 05/24] Add test script Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch b/packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch index f48c410..4de298e 100644 --- a/packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch +++ b/packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch @@ -1,7 +1,7 @@ -From 18b97cfb2401d3c5325250dbf6c339ef873cd515 Mon Sep 17 00:00:00 2001 +From 18dc29d6776d5d888db1b78fa936545fb98c67d1 Mon Sep 17 00:00:00 2001 From: Rory& Date: Mon, 9 Jun 2025 17:46:10 +0200 -Subject: [PATCH 06/25] Fix gitignore to ignore .venv +Subject: [PATCH 06/24] Fix gitignore to ignore .venv Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch b/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch index 57d382f..db96ea1 100644 --- a/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch +++ b/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch @@ -1,7 +1,7 @@ -From 4f0166fdae36a7bde86576ce4cac25b9d5ba7a98 Mon Sep 17 00:00:00 2001 +From 669cab3576182215852c9c2a06739106f1aecc14 Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 22 Jul 2025 05:07:01 +0200 -Subject: [PATCH 07/25] Fast auth links +Subject: [PATCH 07/24] Fast auth links Signed-off-by: Rory& --- diff --git a/packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch b/packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch index d05f91e..f81e616 100644 --- a/packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch +++ b/packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch @@ -1,7 +1,7 @@ -From b52d3a34f03fc5307f44faf3ec85dd73a785bd4e Mon Sep 17 00:00:00 2001 +From 5b827992d4c68a0ff31e544cf28f78541b16bf74 Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 23 Apr 2025 17:53:52 +0200 -Subject: [PATCH 08/25] Add too much logging to room summary over federation +Subject: [PATCH 08/24] Add too much logging to room summary over federation Signed-off-by: Rory& --- @@ -9,10 +9,10 @@ Signed-off-by: Rory& 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/synapse/handlers/room_summary.py b/synapse/handlers/room_summary.py -index 9ec0d33f11..5ea32af620 100644 +index bbcdc0877e..5a35d4d0e1 100644 --- a/synapse/handlers/room_summary.py +++ b/synapse/handlers/room_summary.py -@@ -748,23 +748,55 @@ class RoomSummaryHandler: +@@ -747,23 +747,55 @@ class RoomSummaryHandler: """ # The API doesn't return the room version so assume that a # join rule of knock is valid. diff --git a/packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch b/packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch index 3250311..71ab64b 100644 --- a/packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch +++ b/packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch @@ -1,7 +1,7 @@ -From 2b3a5ce99695c14ac92c74f17c3ee47ae7d1f93f Mon Sep 17 00:00:00 2001 +From dfc816f4f3a2a457013013d4415a99d60d971289 Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 23 Apr 2025 18:24:57 +0200 -Subject: [PATCH 09/25] Log entire room if accessibility check fails +Subject: [PATCH 09/24] Log entire room if accessibility check fails Signed-off-by: Rory& --- @@ -9,10 +9,10 @@ Signed-off-by: Rory& 1 file changed, 4 insertions(+) diff --git a/synapse/handlers/room_summary.py b/synapse/handlers/room_summary.py -index 5ea32af620..30ee91cd95 100644 +index 5a35d4d0e1..9f904af2ea 100644 --- a/synapse/handlers/room_summary.py +++ b/synapse/handlers/room_summary.py -@@ -964,6 +964,10 @@ class RoomSummaryHandler: +@@ -961,6 +961,10 @@ class RoomSummaryHandler: if not room_entry or not await self._is_remote_room_accessible( requester, room_entry.room_id, room_entry.room ): diff --git a/packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch b/packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch deleted file mode 100644 index b75501f..0000000 --- a/packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch +++ /dev/null @@ -1,31 +0,0 @@ -From a9ec590c5d767bd833c868eda6490c10396b9bbc Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 05:21:46 +0200 -Subject: [PATCH 10/25] Log policy server rejected events - -Signed-off-by: Rory& ---- - synapse/handlers/room_policy.py | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/synapse/handlers/room_policy.py b/synapse/handlers/room_policy.py -index 0663a36714..d2216978ac 100644 ---- a/synapse/handlers/room_policy.py -+++ b/synapse/handlers/room_policy.py -@@ -111,6 +111,13 @@ class RoomPolicyHandler: - policy_server, event - ) - if recommendation != RECOMMENDATION_OK: -+ logger.info( -+ "[POLICY] Policy server %s recommended not to allow event %s in room %s: %s", -+ policy_server, -+ event.event_id, -+ event.room_id, -+ recommendation, -+ ) - return False - - return True # default allow --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0010-Use-parse_boolean-for-unredacted-content.patch b/packages/overlays/matrix-synapse/patches/0010-Use-parse_boolean-for-unredacted-content.patch new file mode 100644 index 0000000..fe73296 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0010-Use-parse_boolean-for-unredacted-content.patch @@ -0,0 +1,30 @@ +From f2d18127aff7ad78db1f868e53fe804d70bbe981 Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Tue, 27 May 2025 06:14:26 +0200 +Subject: [PATCH 10/24] Use parse_boolean for unredacted content + +Signed-off-by: Rory& +--- + synapse/rest/client/room.py | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py +index 65d9c130ef..a395f1d693 100644 +--- a/synapse/rest/client/room.py ++++ b/synapse/rest/client/room.py +@@ -1050,10 +1050,9 @@ class RoomEventServlet(RestServlet): + requester = await self.auth.get_user_by_req(request, allow_guest=True) + + include_unredacted_content = self.msc2815_enabled and ( +- parse_string( ++ parse_boolean( + request, +- "fi.mau.msc2815.include_unredacted_content", +- allowed_values=("true", "false"), ++ "fi.mau.msc2815.include_unredacted_content" + ) + == "true" + ) +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0011-Expose-tombstone-in-room-admin-api.patch b/packages/overlays/matrix-synapse/patches/0011-Expose-tombstone-in-room-admin-api.patch new file mode 100644 index 0000000..074c615 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0011-Expose-tombstone-in-room-admin-api.patch @@ -0,0 +1,115 @@ +From 216293bb5649fab432fbec34af80e68ff2217276 Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Tue, 27 May 2025 06:37:52 +0200 +Subject: [PATCH 11/24] Expose tombstone in room admin api + +Signed-off-by: Rory& +--- + synapse/rest/admin/rooms.py | 5 ++++ + synapse/rest/client/room.py | 1 - + synapse/storage/databases/main/room.py | 36 +++++++++++++++++++++++++- + 3 files changed, 40 insertions(+), 2 deletions(-) + +diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py +index a886859ffa..e97d076a44 100644 +--- a/synapse/rest/admin/rooms.py ++++ b/synapse/rest/admin/rooms.py +@@ -301,6 +301,10 @@ class ListRoomRestServlet(RestServlet): + direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS) + reverse_order = True if direction == Direction.BACKWARDS else False + ++ emma_include_tombstone = parse_boolean( ++ request, "emma_include_tombstone", default=False ++ ) ++ + # Return list of rooms according to parameters + rooms, total_rooms = await self.store.get_rooms_paginate( + start, +@@ -310,6 +314,7 @@ class ListRoomRestServlet(RestServlet): + search_term, + public_rooms, + empty_rooms, ++ emma_include_tombstone = emma_include_tombstone + ) + + response = { +diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py +index a395f1d693..6631e629db 100644 +--- a/synapse/rest/client/room.py ++++ b/synapse/rest/client/room.py +@@ -1054,7 +1054,6 @@ class RoomEventServlet(RestServlet): + request, + "fi.mau.msc2815.include_unredacted_content" + ) +- == "true" + ) + if include_unredacted_content and not await self.auth.is_server_admin( + requester +diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py +index 7ac88e4c2a..ff647c0c5b 100644 +--- a/synapse/storage/databases/main/room.py ++++ b/synapse/storage/databases/main/room.py +@@ -605,6 +605,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + search_term: str | None, + public_rooms: bool | None, + empty_rooms: bool | None, ++ emma_include_tombstone: bool = False, + ) -> tuple[list[dict[str, Any]], int]: + """Function to retrieve a paginated list of rooms as json. + +@@ -624,6 +625,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + If true, empty rooms are queried. + if false, empty rooms are excluded from the query. When it is + none (the default), both empty rooms and none-empty rooms are queried. ++ emma_include_tombstone: If true, include tombstone events in the results. + Returns: + A list of room dicts and an integer representing the total number of + rooms that exist given this query +@@ -792,11 +794,43 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + room_count = cast(tuple[int], txn.fetchone()) + return rooms, room_count[0] + +- return await self.db_pool.runInteraction( ++ result = await self.db_pool.runInteraction( + "get_rooms_paginate", + _get_rooms_paginate_txn, + ) + ++ if emma_include_tombstone: ++ room_id_sql, room_id_args = make_in_list_sql_clause( ++ self.database_engine, "cse.room_id", [r["room_id"] for r in result[0]] ++ ) ++ ++ tombstone_sql = """ ++ SELECT cse.room_id, cse.event_id, ej.json ++ FROM current_state_events cse ++ JOIN event_json ej USING (event_id) ++ WHERE cse.type = 'm.room.tombstone' ++ AND {room_id_sql} ++ """.format( ++ room_id_sql=room_id_sql ++ ) ++ ++ def _get_tombstones_txn( ++ txn: LoggingTransaction, ++ ) -> Dict[str, JsonDict]: ++ txn.execute(tombstone_sql, room_id_args) ++ for room_id, event_id, json in txn: ++ for result_room in result[0]: ++ if result_room["room_id"] == room_id: ++ result_room["gay.rory.synapse_admin_extensions.tombstone"] = db_to_json(json) ++ break ++ return result[0], result[1] ++ ++ result = await self.db_pool.runInteraction( ++ "get_rooms_tombstones", _get_tombstones_txn, ++ ) ++ ++ return result ++ + @cached(max_entries=10000) + async def get_ratelimit_for_user(self, user_id: str) -> RatelimitOverride | None: + """Check if there are any overrides for ratelimiting for the given user +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch b/packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch deleted file mode 100644 index 80ddb8f..0000000 --- a/packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch +++ /dev/null @@ -1,30 +0,0 @@ -From e7ef63ba7d64a1aedb7455ed0e9492397e97b00b Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 06:14:26 +0200 -Subject: [PATCH 11/25] Use parse_boolean for unredacted content - -Signed-off-by: Rory& ---- - synapse/rest/client/room.py | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py -index 9172bfcb4e..07df0bbbee 100644 ---- a/synapse/rest/client/room.py -+++ b/synapse/rest/client/room.py -@@ -1050,10 +1050,9 @@ class RoomEventServlet(RestServlet): - requester = await self.auth.get_user_by_req(request, allow_guest=True) - - include_unredacted_content = self.msc2815_enabled and ( -- parse_string( -+ parse_boolean( - request, -- "fi.mau.msc2815.include_unredacted_content", -- allowed_values=("true", "false"), -+ "fi.mau.msc2815.include_unredacted_content" - ) - == "true" - ) --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch b/packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch deleted file mode 100644 index 0efe0b4..0000000 --- a/packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch +++ /dev/null @@ -1,115 +0,0 @@ -From e3287a79f2164c1966cfbc91138f6f2c8515af0c Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 06:37:52 +0200 -Subject: [PATCH 12/25] Expose tombstone in room admin api - -Signed-off-by: Rory& ---- - synapse/rest/admin/rooms.py | 5 ++++ - synapse/rest/client/room.py | 1 - - synapse/storage/databases/main/room.py | 36 +++++++++++++++++++++++++- - 3 files changed, 40 insertions(+), 2 deletions(-) - -diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py -index a886859ffa..e97d076a44 100644 ---- a/synapse/rest/admin/rooms.py -+++ b/synapse/rest/admin/rooms.py -@@ -301,6 +301,10 @@ class ListRoomRestServlet(RestServlet): - direction = parse_enum(request, "dir", Direction, default=Direction.FORWARDS) - reverse_order = True if direction == Direction.BACKWARDS else False - -+ emma_include_tombstone = parse_boolean( -+ request, "emma_include_tombstone", default=False -+ ) -+ - # Return list of rooms according to parameters - rooms, total_rooms = await self.store.get_rooms_paginate( - start, -@@ -310,6 +314,7 @@ class ListRoomRestServlet(RestServlet): - search_term, - public_rooms, - empty_rooms, -+ emma_include_tombstone = emma_include_tombstone - ) - - response = { -diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py -index 07df0bbbee..875890470e 100644 ---- a/synapse/rest/client/room.py -+++ b/synapse/rest/client/room.py -@@ -1054,7 +1054,6 @@ class RoomEventServlet(RestServlet): - request, - "fi.mau.msc2815.include_unredacted_content" - ) -- == "true" - ) - if include_unredacted_content and not await self.auth.is_server_admin( - requester -diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py -index 633df07736..7623208c28 100644 ---- a/synapse/storage/databases/main/room.py -+++ b/synapse/storage/databases/main/room.py -@@ -605,6 +605,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - search_term: str | None, - public_rooms: bool | None, - empty_rooms: bool | None, -+ emma_include_tombstone: bool = False, - ) -> tuple[list[dict[str, Any]], int]: - """Function to retrieve a paginated list of rooms as json. - -@@ -624,6 +625,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - If true, empty rooms are queried. - if false, empty rooms are excluded from the query. When it is - none (the default), both empty rooms and none-empty rooms are queried. -+ emma_include_tombstone: If true, include tombstone events in the results. - Returns: - A list of room dicts and an integer representing the total number of - rooms that exist given this query -@@ -792,11 +794,43 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - room_count = cast(tuple[int], txn.fetchone()) - return rooms, room_count[0] - -- return await self.db_pool.runInteraction( -+ result = await self.db_pool.runInteraction( - "get_rooms_paginate", - _get_rooms_paginate_txn, - ) - -+ if emma_include_tombstone: -+ room_id_sql, room_id_args = make_in_list_sql_clause( -+ self.database_engine, "cse.room_id", [r["room_id"] for r in result[0]] -+ ) -+ -+ tombstone_sql = """ -+ SELECT cse.room_id, cse.event_id, ej.json -+ FROM current_state_events cse -+ JOIN event_json ej USING (event_id) -+ WHERE cse.type = 'm.room.tombstone' -+ AND {room_id_sql} -+ """.format( -+ room_id_sql=room_id_sql -+ ) -+ -+ def _get_tombstones_txn( -+ txn: LoggingTransaction, -+ ) -> Dict[str, JsonDict]: -+ txn.execute(tombstone_sql, room_id_args) -+ for room_id, event_id, json in txn: -+ for result_room in result[0]: -+ if result_room["room_id"] == room_id: -+ result_room["gay.rory.synapse_admin_extensions.tombstone"] = db_to_json(json) -+ break -+ return result[0], result[1] -+ -+ result = await self.db_pool.runInteraction( -+ "get_rooms_tombstones", _get_tombstones_txn, -+ ) -+ -+ return result -+ - @cached(max_entries=10000) - async def get_ratelimit_for_user(self, user_id: str) -> RatelimitOverride | None: - """Check if there are any overrides for ratelimiting for the given user --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0012-fix-Always-recheck-messages-pagination-data-if-a-bac.patch b/packages/overlays/matrix-synapse/patches/0012-fix-Always-recheck-messages-pagination-data-if-a-bac.patch new file mode 100644 index 0000000..e666cfe --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0012-fix-Always-recheck-messages-pagination-data-if-a-bac.patch @@ -0,0 +1,196 @@ +From 6c31b7de9e5d860a223e0a73749276048fd9f617 Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Wed, 30 Apr 2025 09:29:42 -0500 +Subject: [PATCH 12/24] fix: Always recheck `/messages` pagination data if a + backfill might have been needed (#28) + +Signed-off-by: Rory& +--- + synapse/handlers/federation.py | 33 ++++++++++++------------------- + synapse/handlers/pagination.py | 36 +++++++++++++++++++--------------- + 2 files changed, 33 insertions(+), 36 deletions(-) + +diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py +index 14805ac80f..04f8587fd8 100644 +--- a/synapse/handlers/federation.py ++++ b/synapse/handlers/federation.py +@@ -191,7 +191,7 @@ class FederationHandler: + @tag_args + async def maybe_backfill( + self, room_id: str, current_depth: int, limit: int, record_time: bool = True +- ) -> bool: ++ ) -> None: + """Checks the database to see if we should backfill before paginating, + and if so do. + +@@ -205,8 +205,6 @@ class FederationHandler: + should back paginate. + record_time: Whether to record the time it takes to backfill. + +- Returns: +- True if we actually tried to backfill something, otherwise False. + """ + # Starting the processing time here so we can include the room backfill + # linearizer lock queue in the timing +@@ -306,7 +304,7 @@ class FederationHandler: + limit=1, + ) + if not have_later_backfill_points: +- return False ++ return None + + logger.debug( + "_maybe_backfill_inner: all backfill points are *after* current depth. Trying again with later backfill points." +@@ -326,15 +324,15 @@ class FederationHandler: + ) + # We return `False` because we're backfilling in the background and there is + # no new events immediately for the caller to know about yet. +- return False ++ return None + + # Even after recursing with `MAX_DEPTH`, we didn't find any + # backward extremities to backfill from. + if not sorted_backfill_points: + logger.debug( +- "_maybe_backfill_inner: Not backfilling as no backward extremeties found." ++ "_maybe_backfill_inner: Not backfilling as no backward extremities found." + ) +- return False ++ return None + + # If we're approaching an extremity we trigger a backfill, otherwise we + # no-op. +@@ -353,7 +351,7 @@ class FederationHandler: + current_depth, + limit, + ) +- return False ++ return None + + # For performance's sake, we only want to paginate from a particular extremity + # if we can actually see the events we'll get. Otherwise, we'd just spend a lot +@@ -421,7 +419,7 @@ class FederationHandler: + logger.debug( + "_maybe_backfill_inner: found no extremities which would be visible" + ) +- return False ++ return None + + logger.debug( + "_maybe_backfill_inner: extremities_to_request %s", extremities_to_request +@@ -444,7 +442,7 @@ class FederationHandler: + ) + ) + +- async def try_backfill(domains: StrCollection) -> bool: ++ async def try_backfill(domains: StrCollection) -> None: + # TODO: Should we try multiple of these at a time? + + # Number of contacted remote homeservers that have denied our backfill +@@ -467,7 +465,7 @@ class FederationHandler: + # If this succeeded then we probably already have the + # appropriate stuff. + # TODO: We can probably do something more intelligent here. +- return True ++ return None + except NotRetryingDestination as e: + logger.info("_maybe_backfill_inner: %s", e) + continue +@@ -491,7 +489,7 @@ class FederationHandler: + ) + denied_count += 1 + if denied_count >= max_denied_count: +- return False ++ return None + continue + + logger.info("Failed to backfill from %s because %s", dom, e) +@@ -507,7 +505,7 @@ class FederationHandler: + ) + denied_count += 1 + if denied_count >= max_denied_count: +- return False ++ return None + continue + + logger.info("Failed to backfill from %s because %s", dom, e) +@@ -519,7 +517,7 @@ class FederationHandler: + logger.exception("Failed to backfill from %s because %s", dom, e) + continue + +- return False ++ return None + + # If we have the `processing_start_time`, then we can make an + # observation. We wouldn't have the `processing_start_time` in the case +@@ -531,14 +529,9 @@ class FederationHandler: + **{SERVER_NAME_LABEL: self.server_name} + ).observe((processing_end_time - processing_start_time) / 1000) + +- success = await try_backfill(likely_domains) +- if success: +- return True +- + # TODO: we could also try servers which were previously in the room, but + # are no longer. +- +- return False ++ return await try_backfill(likely_domains) + + async def send_invite(self, target_host: str, event: EventBase) -> EventBase: + """Sends the invite to the remote server for signing. +diff --git a/synapse/handlers/pagination.py b/synapse/handlers/pagination.py +index 7b9c829056..368fa3e007 100644 +--- a/synapse/handlers/pagination.py ++++ b/synapse/handlers/pagination.py +@@ -632,27 +632,31 @@ class PaginationHandler: + or missing_too_many_events + or not_enough_events_to_fill_response + ): +- did_backfill = await self.hs.get_federation_handler().maybe_backfill( ++ # Historical Note: There used to be a check here for if backfill was ++ # successful or not ++ await self.hs.get_federation_handler().maybe_backfill( + room_id, + curr_topo, + limit=pagin_config.limit, + ) + +- # If we did backfill something, refetch the events from the database to +- # catch anything new that might have been added since we last fetched. +- if did_backfill: +- ( +- events, +- next_key, +- _, +- ) = await self.store.paginate_room_events_by_topological_ordering( +- room_id=room_id, +- from_key=from_token.room_key, +- to_key=to_room_key, +- direction=pagin_config.direction, +- limit=pagin_config.limit, +- event_filter=event_filter, +- ) ++ # Regardless if we backfilled or not, another worker or even a ++ # simultaneous request may have backfilled for us while we were held ++ # behind the linearizer. This should not have too much additional ++ # database load as it will only be triggered if a backfill *might* have ++ # been needed ++ ( ++ events, ++ next_key, ++ _, ++ ) = await self.store.paginate_room_events_by_topological_ordering( ++ room_id=room_id, ++ from_key=from_token.room_key, ++ to_key=to_room_key, ++ direction=pagin_config.direction, ++ limit=pagin_config.limit, ++ event_filter=event_filter, ++ ) + else: + # Otherwise, we can backfill in the background for eventual + # consistency's sake but we don't need to block the client waiting +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0013-Fix-pagination-with-large-gaps-of-rejected-events.patch b/packages/overlays/matrix-synapse/patches/0013-Fix-pagination-with-large-gaps-of-rejected-events.patch new file mode 100644 index 0000000..b82b2ba --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0013-Fix-pagination-with-large-gaps-of-rejected-events.patch @@ -0,0 +1,51 @@ +From c960746dd2eb364ed647e2ac1ce67db938e8d6f5 Mon Sep 17 00:00:00 2001 +From: Nicolas Werner +Date: Sun, 8 Jun 2025 23:14:31 +0200 +Subject: [PATCH 13/24] Fix pagination with large gaps of rejected events + +Signed-off-by: Rory& +--- + synapse/handlers/pagination.py | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/synapse/handlers/pagination.py b/synapse/handlers/pagination.py +index 368fa3e007..350c1ccd9e 100644 +--- a/synapse/handlers/pagination.py ++++ b/synapse/handlers/pagination.py +@@ -565,7 +565,7 @@ class PaginationHandler: + ( + events, + next_key, +- _, ++ limited, + ) = await self.store.paginate_room_events_by_topological_ordering( + room_id=room_id, + from_key=from_token.room_key, +@@ -648,7 +648,7 @@ class PaginationHandler: + ( + events, + next_key, +- _, ++ limited, + ) = await self.store.paginate_room_events_by_topological_ordering( + room_id=room_id, + from_key=from_token.room_key, +@@ -671,6 +671,15 @@ class PaginationHandler: + + next_token = from_token.copy_and_replace(StreamKeyType.ROOM, next_key) + ++ # We might have hit some internal filtering first, for example rejected ++ # events. Ensure we return a pagination token then. ++ if not events and limited: ++ return { ++ "chunk": [], ++ "start": await from_token.to_string(self.store), ++ "end": await next_token.to_string(self.store), ++ } ++ + # if no events are returned from pagination, that implies + # we have reached the end of the available events. + # In that case we do not return end, to tell the client +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0013-fix-Always-recheck-messages-pagination-data-if-a-bac.patch b/packages/overlays/matrix-synapse/patches/0013-fix-Always-recheck-messages-pagination-data-if-a-bac.patch deleted file mode 100644 index 88657d7..0000000 --- a/packages/overlays/matrix-synapse/patches/0013-fix-Always-recheck-messages-pagination-data-if-a-bac.patch +++ /dev/null @@ -1,196 +0,0 @@ -From efb007f59d2b9426b806ad2ed891410a290626e2 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Wed, 30 Apr 2025 09:29:42 -0500 -Subject: [PATCH 13/25] fix: Always recheck `/messages` pagination data if a - backfill might have been needed (#28) - -Signed-off-by: Rory& ---- - synapse/handlers/federation.py | 33 ++++++++++++------------------- - synapse/handlers/pagination.py | 36 +++++++++++++++++++--------------- - 2 files changed, 33 insertions(+), 36 deletions(-) - -diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py -index 14805ac80f..04f8587fd8 100644 ---- a/synapse/handlers/federation.py -+++ b/synapse/handlers/federation.py -@@ -191,7 +191,7 @@ class FederationHandler: - @tag_args - async def maybe_backfill( - self, room_id: str, current_depth: int, limit: int, record_time: bool = True -- ) -> bool: -+ ) -> None: - """Checks the database to see if we should backfill before paginating, - and if so do. - -@@ -205,8 +205,6 @@ class FederationHandler: - should back paginate. - record_time: Whether to record the time it takes to backfill. - -- Returns: -- True if we actually tried to backfill something, otherwise False. - """ - # Starting the processing time here so we can include the room backfill - # linearizer lock queue in the timing -@@ -306,7 +304,7 @@ class FederationHandler: - limit=1, - ) - if not have_later_backfill_points: -- return False -+ return None - - logger.debug( - "_maybe_backfill_inner: all backfill points are *after* current depth. Trying again with later backfill points." -@@ -326,15 +324,15 @@ class FederationHandler: - ) - # We return `False` because we're backfilling in the background and there is - # no new events immediately for the caller to know about yet. -- return False -+ return None - - # Even after recursing with `MAX_DEPTH`, we didn't find any - # backward extremities to backfill from. - if not sorted_backfill_points: - logger.debug( -- "_maybe_backfill_inner: Not backfilling as no backward extremeties found." -+ "_maybe_backfill_inner: Not backfilling as no backward extremities found." - ) -- return False -+ return None - - # If we're approaching an extremity we trigger a backfill, otherwise we - # no-op. -@@ -353,7 +351,7 @@ class FederationHandler: - current_depth, - limit, - ) -- return False -+ return None - - # For performance's sake, we only want to paginate from a particular extremity - # if we can actually see the events we'll get. Otherwise, we'd just spend a lot -@@ -421,7 +419,7 @@ class FederationHandler: - logger.debug( - "_maybe_backfill_inner: found no extremities which would be visible" - ) -- return False -+ return None - - logger.debug( - "_maybe_backfill_inner: extremities_to_request %s", extremities_to_request -@@ -444,7 +442,7 @@ class FederationHandler: - ) - ) - -- async def try_backfill(domains: StrCollection) -> bool: -+ async def try_backfill(domains: StrCollection) -> None: - # TODO: Should we try multiple of these at a time? - - # Number of contacted remote homeservers that have denied our backfill -@@ -467,7 +465,7 @@ class FederationHandler: - # If this succeeded then we probably already have the - # appropriate stuff. - # TODO: We can probably do something more intelligent here. -- return True -+ return None - except NotRetryingDestination as e: - logger.info("_maybe_backfill_inner: %s", e) - continue -@@ -491,7 +489,7 @@ class FederationHandler: - ) - denied_count += 1 - if denied_count >= max_denied_count: -- return False -+ return None - continue - - logger.info("Failed to backfill from %s because %s", dom, e) -@@ -507,7 +505,7 @@ class FederationHandler: - ) - denied_count += 1 - if denied_count >= max_denied_count: -- return False -+ return None - continue - - logger.info("Failed to backfill from %s because %s", dom, e) -@@ -519,7 +517,7 @@ class FederationHandler: - logger.exception("Failed to backfill from %s because %s", dom, e) - continue - -- return False -+ return None - - # If we have the `processing_start_time`, then we can make an - # observation. We wouldn't have the `processing_start_time` in the case -@@ -531,14 +529,9 @@ class FederationHandler: - **{SERVER_NAME_LABEL: self.server_name} - ).observe((processing_end_time - processing_start_time) / 1000) - -- success = await try_backfill(likely_domains) -- if success: -- return True -- - # TODO: we could also try servers which were previously in the room, but - # are no longer. -- -- return False -+ return await try_backfill(likely_domains) - - async def send_invite(self, target_host: str, event: EventBase) -> EventBase: - """Sends the invite to the remote server for signing. -diff --git a/synapse/handlers/pagination.py b/synapse/handlers/pagination.py -index 7b9c829056..368fa3e007 100644 ---- a/synapse/handlers/pagination.py -+++ b/synapse/handlers/pagination.py -@@ -632,27 +632,31 @@ class PaginationHandler: - or missing_too_many_events - or not_enough_events_to_fill_response - ): -- did_backfill = await self.hs.get_federation_handler().maybe_backfill( -+ # Historical Note: There used to be a check here for if backfill was -+ # successful or not -+ await self.hs.get_federation_handler().maybe_backfill( - room_id, - curr_topo, - limit=pagin_config.limit, - ) - -- # If we did backfill something, refetch the events from the database to -- # catch anything new that might have been added since we last fetched. -- if did_backfill: -- ( -- events, -- next_key, -- _, -- ) = await self.store.paginate_room_events_by_topological_ordering( -- room_id=room_id, -- from_key=from_token.room_key, -- to_key=to_room_key, -- direction=pagin_config.direction, -- limit=pagin_config.limit, -- event_filter=event_filter, -- ) -+ # Regardless if we backfilled or not, another worker or even a -+ # simultaneous request may have backfilled for us while we were held -+ # behind the linearizer. This should not have too much additional -+ # database load as it will only be triggered if a backfill *might* have -+ # been needed -+ ( -+ events, -+ next_key, -+ _, -+ ) = await self.store.paginate_room_events_by_topological_ordering( -+ room_id=room_id, -+ from_key=from_token.room_key, -+ to_key=to_room_key, -+ direction=pagin_config.direction, -+ limit=pagin_config.limit, -+ event_filter=event_filter, -+ ) - else: - # Otherwise, we can backfill in the background for eventual - # consistency's sake but we don't need to block the client waiting --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch b/packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch deleted file mode 100644 index 25cabea..0000000 --- a/packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch +++ /dev/null @@ -1,51 +0,0 @@ -From 2a6a3836c68cdf3aa1841168cb847c552a14110e Mon Sep 17 00:00:00 2001 -From: Nicolas Werner -Date: Sun, 8 Jun 2025 23:14:31 +0200 -Subject: [PATCH 14/25] Fix pagination with large gaps of rejected events - -Signed-off-by: Rory& ---- - synapse/handlers/pagination.py | 13 +++++++++++-- - 1 file changed, 11 insertions(+), 2 deletions(-) - -diff --git a/synapse/handlers/pagination.py b/synapse/handlers/pagination.py -index 368fa3e007..350c1ccd9e 100644 ---- a/synapse/handlers/pagination.py -+++ b/synapse/handlers/pagination.py -@@ -565,7 +565,7 @@ class PaginationHandler: - ( - events, - next_key, -- _, -+ limited, - ) = await self.store.paginate_room_events_by_topological_ordering( - room_id=room_id, - from_key=from_token.room_key, -@@ -648,7 +648,7 @@ class PaginationHandler: - ( - events, - next_key, -- _, -+ limited, - ) = await self.store.paginate_room_events_by_topological_ordering( - room_id=room_id, - from_key=from_token.room_key, -@@ -671,6 +671,15 @@ class PaginationHandler: - - next_token = from_token.copy_and_replace(StreamKeyType.ROOM, next_key) - -+ # We might have hit some internal filtering first, for example rejected -+ # events. Ensure we return a pagination token then. -+ if not events and limited: -+ return { -+ "chunk": [], -+ "start": await from_token.to_string(self.store), -+ "end": await next_token.to_string(self.store), -+ } -+ - # if no events are returned from pagination, that implies - # we have reached the end of the available events. - # In that case we do not return end, to tell the client --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0014-RequestRatelimiter-expose-can_do_action.patch b/packages/overlays/matrix-synapse/patches/0014-RequestRatelimiter-expose-can_do_action.patch new file mode 100644 index 0000000..266135b --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0014-RequestRatelimiter-expose-can_do_action.patch @@ -0,0 +1,96 @@ +From 79ebebba08a1ff26d93606daf808f7c07cbf19b2 Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Fri, 25 Jul 2025 08:26:15 +0200 +Subject: [PATCH 14/24] RequestRatelimiter: expose can_do_action + +Signed-off-by: Rory& +--- + synapse/api/ratelimiting.py | 75 +++++++++++++++++++++++++++++++++++++ + 1 file changed, 75 insertions(+) + +diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py +index d6cc3d26b5..bdc9481e4f 100644 +--- a/synapse/api/ratelimiting.py ++++ b/synapse/api/ratelimiting.py +@@ -479,3 +479,78 @@ class RequestRatelimiter: + update=update, + n_actions=n_actions, + ) ++ ++ async def can_do_action( ++ self, ++ requester: Optional[Requester], ++ burst_count: Optional[int] = None, ++ update: bool = True, ++ is_admin_redaction: bool = False, ++ n_actions: int = 1, ++ ) -> tuple[bool, float]: ++ """Can the entity (e.g. user or IP address) perform the action? ++ ++ Checks if the user has ratelimiting disabled in the database by looking ++ for null/zero values in the `ratelimit_override` table. (Non-zero ++ values aren't honoured, as they're specific to the event sending ++ ratelimiter, rather than all ratelimiters) ++ ++ Args: ++ requester: The requester that is doing the action, if any. Used to check ++ if the user has ratelimits disabled in the database. ++ key: An arbitrary key used to classify an action. Defaults to the ++ requester's user ID. ++ rate_hz: The long term number of actions that can be performed in a second. ++ Overrides the value set during instantiation if set. ++ burst_count: How many actions that can be performed before being limited. ++ Overrides the value set during instantiation if set. ++ update: Whether to count this check as performing the action. If the action ++ cannot be performed, the user's action count is not incremented at all. ++ n_actions: The number of times the user wants to do this action. If the user ++ cannot do all of the actions, the user's action count is not incremented ++ at all. ++ _time_now_s: The current time. Optional, defaults to the current time according ++ to self.clock. Only used by tests. ++ ++ Returns: ++ A tuple containing: ++ * A bool indicating if they can perform the action now ++ * The reactor timestamp for when the action can be performed next. ++ -1 if rate_hz is less than or equal to zero ++ """ ++ user_id = requester.user.to_string() ++ ++ # The AS user itself is never rate limited. ++ app_service = self.store.get_app_service_by_user_id(user_id) ++ if app_service is not None: ++ return True, 0 # do not ratelimit app service senders ++ ++ messages_per_second = self._rc_message.per_second ++ burst_count = self._rc_message.burst_count ++ ++ # Check if there is a per user override in the DB. ++ override = await self.store.get_ratelimit_for_user(user_id) ++ if override: ++ # If overridden with a null Hz then ratelimiting has been entirely ++ # disabled for the user ++ if not override.messages_per_second: ++ return True, 0 ++ ++ messages_per_second = override.messages_per_second ++ burst_count = override.burst_count ++ ++ if is_admin_redaction and self.admin_redaction_ratelimiter: ++ # If we have separate config for admin redactions, use a separate ++ # ratelimiter as to not have user_ids clash ++ return await self.admin_redaction_ratelimiter.can_do_action( ++ requester, update=update, n_actions=n_actions ++ ) ++ else: ++ # Override rate and burst count per-user ++ return await self.request_ratelimiter.can_do_action( ++ requester, ++ rate_hz=messages_per_second, ++ burst_count=burst_count, ++ update=update, ++ n_actions=n_actions, ++ ) +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0015-Clarify-pre_event_ids-assert-in-event-creation-handl.patch b/packages/overlays/matrix-synapse/patches/0015-Clarify-pre_event_ids-assert-in-event-creation-handl.patch new file mode 100644 index 0000000..f476f33 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0015-Clarify-pre_event_ids-assert-in-event-creation-handl.patch @@ -0,0 +1,26 @@ +From 5281f1a0db81a620113452176a1ef8194d1e8e4a Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Sat, 16 Aug 2025 20:19:08 +0200 +Subject: [PATCH 15/24] Clarify pre_event_ids assert in event creation handler + +Signed-off-by: Rory& +--- + synapse/handlers/message.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py +index eb01622515..65782dc459 100644 +--- a/synapse/handlers/message.py ++++ b/synapse/handlers/message.py +@@ -1285,7 +1285,7 @@ class EventCreationHandler: + if state_event_ids is not None: + # Do a quick check to make sure that prev_event_ids is present to + # make the type-checking around `builder.build` happy. +- assert prev_event_ids is not None ++ assert prev_event_ids is not None, "create_new_client_event called with state_event_ids but no prev_event_ids" + + temp_event = await builder.build( + prev_event_ids=prev_event_ids, +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch b/packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch deleted file mode 100644 index 6b43100..0000000 --- a/packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch +++ /dev/null @@ -1,96 +0,0 @@ -From 5433142126b82a063aa520245386830cbbe78357 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Fri, 25 Jul 2025 08:26:15 +0200 -Subject: [PATCH 15/25] RequestRatelimiter: expose can_do_action - -Signed-off-by: Rory& ---- - synapse/api/ratelimiting.py | 75 +++++++++++++++++++++++++++++++++++++ - 1 file changed, 75 insertions(+) - -diff --git a/synapse/api/ratelimiting.py b/synapse/api/ratelimiting.py -index d6cc3d26b5..bdc9481e4f 100644 ---- a/synapse/api/ratelimiting.py -+++ b/synapse/api/ratelimiting.py -@@ -479,3 +479,78 @@ class RequestRatelimiter: - update=update, - n_actions=n_actions, - ) -+ -+ async def can_do_action( -+ self, -+ requester: Optional[Requester], -+ burst_count: Optional[int] = None, -+ update: bool = True, -+ is_admin_redaction: bool = False, -+ n_actions: int = 1, -+ ) -> tuple[bool, float]: -+ """Can the entity (e.g. user or IP address) perform the action? -+ -+ Checks if the user has ratelimiting disabled in the database by looking -+ for null/zero values in the `ratelimit_override` table. (Non-zero -+ values aren't honoured, as they're specific to the event sending -+ ratelimiter, rather than all ratelimiters) -+ -+ Args: -+ requester: The requester that is doing the action, if any. Used to check -+ if the user has ratelimits disabled in the database. -+ key: An arbitrary key used to classify an action. Defaults to the -+ requester's user ID. -+ rate_hz: The long term number of actions that can be performed in a second. -+ Overrides the value set during instantiation if set. -+ burst_count: How many actions that can be performed before being limited. -+ Overrides the value set during instantiation if set. -+ update: Whether to count this check as performing the action. If the action -+ cannot be performed, the user's action count is not incremented at all. -+ n_actions: The number of times the user wants to do this action. If the user -+ cannot do all of the actions, the user's action count is not incremented -+ at all. -+ _time_now_s: The current time. Optional, defaults to the current time according -+ to self.clock. Only used by tests. -+ -+ Returns: -+ A tuple containing: -+ * A bool indicating if they can perform the action now -+ * The reactor timestamp for when the action can be performed next. -+ -1 if rate_hz is less than or equal to zero -+ """ -+ user_id = requester.user.to_string() -+ -+ # The AS user itself is never rate limited. -+ app_service = self.store.get_app_service_by_user_id(user_id) -+ if app_service is not None: -+ return True, 0 # do not ratelimit app service senders -+ -+ messages_per_second = self._rc_message.per_second -+ burst_count = self._rc_message.burst_count -+ -+ # Check if there is a per user override in the DB. -+ override = await self.store.get_ratelimit_for_user(user_id) -+ if override: -+ # If overridden with a null Hz then ratelimiting has been entirely -+ # disabled for the user -+ if not override.messages_per_second: -+ return True, 0 -+ -+ messages_per_second = override.messages_per_second -+ burst_count = override.burst_count -+ -+ if is_admin_redaction and self.admin_redaction_ratelimiter: -+ # If we have separate config for admin redactions, use a separate -+ # ratelimiter as to not have user_ids clash -+ return await self.admin_redaction_ratelimiter.can_do_action( -+ requester, update=update, n_actions=n_actions -+ ) -+ else: -+ # Override rate and burst count per-user -+ return await self.request_ratelimiter.can_do_action( -+ requester, -+ rate_hz=messages_per_second, -+ burst_count=burst_count, -+ update=update, -+ n_actions=n_actions, -+ ) --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0016-Add-bulk-send-events-endpoint.patch b/packages/overlays/matrix-synapse/patches/0016-Add-bulk-send-events-endpoint.patch new file mode 100644 index 0000000..a589d9d --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0016-Add-bulk-send-events-endpoint.patch @@ -0,0 +1,195 @@ +From 20006e0e445baf4d1364ed439cc15050895755cd Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Sat, 26 Jul 2025 09:50:56 +0200 +Subject: [PATCH 16/24] Add bulk send events endpoint + +Try to optimise bulk sending some more + +Further optimisation attempts + +Use create_and_send_new_client_events for bulk sending + +Signed-off-by: Rory& +--- + synapse/rest/client/capabilities.py | 3 + + synapse/rest/client/room.py | 112 +++++++++++++++++++++++++++- + 2 files changed, 114 insertions(+), 1 deletion(-) + +diff --git a/synapse/rest/client/capabilities.py b/synapse/rest/client/capabilities.py +index 705d74dee1..075c3de261 100644 +--- a/synapse/rest/client/capabilities.py ++++ b/synapse/rest/client/capabilities.py +@@ -74,6 +74,9 @@ class CapabilitiesRestServlet(RestServlet): + "m.get_login_token": { + "enabled": self.config.auth.login_via_existing_enabled, + }, ++ "gay.rory.bulk_send_events": { ++ "enabled": True ++ } + } + } + +diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py +index 6631e629db..f21240e346 100644 +--- a/synapse/rest/client/room.py ++++ b/synapse/rest/client/room.py +@@ -23,10 +23,12 @@ + + import logging + import re ++import ijson + from enum import Enum + from http import HTTPStatus + from typing import TYPE_CHECKING, Awaitable + from urllib import parse as urlparse ++from twisted.internet import defer + + import attr + from prometheus_client.core import Histogram +@@ -51,12 +53,14 @@ from synapse.api.errors import ( + UnredactedContentDeletedError, + ) + from synapse.api.filtering import Filter ++from synapse.api.ratelimiting import RequestRatelimiter + from synapse.events.utils import ( + EventClientSerializer, + SerializeEventConfig, + format_event_for_client_v2, + ) + from synapse.handlers.pagination import GetMessagesResult ++from synapse.events import EventBase + from synapse.http.server import HttpServer + from synapse.http.servlet import ( + ResolveRoomIdMixin, +@@ -513,7 +517,6 @@ class RoomSendEventRestServlet(TransactionRestServlet): + txn_id, + ) + +- + def _parse_request_delay( + request: SynapseRequest, + max_delay: int | None, +@@ -1755,6 +1758,112 @@ class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet): + remote_room_hosts, + ) + ++class RoomBulkSendEventRestServlet(ResolveRoomIdMixin, RestServlet): ++ """ ++ Bulk send events to a room. ++ ++ This endpoint allows sending multiple events to a room in a single request, ++ avoiding event linearisation issues. ++ """ ++ ++ PATTERNS = ( ++ re.compile( ++ "^/_matrix/client/unstable/gay.rory.bulk_send_events" ++ "/rooms/(?P[^/]*)/bulk_send_events$" ++ ), ++ ) ++ CATEGORY = "Event sending requests" ++ ++ def __init__(self, hs: "HomeServer"): ++ super().__init__(hs) ++ self._auth = hs.get_auth() ++ self._event_creation_handler = hs.get_event_creation_handler() ++ self._message_handler = hs.get_message_handler() ++ self._storage_controllers = hs.get_storage_controllers() ++ ++ async def wait_ratelimit(ratelimiter: RequestRatelimiter, requester: Requester) -> bool: ++ """ ++ Wait until the ratelimiter allows the requester to proceed. ++ """ ++ ratelimit_hit = False ++ awaiting_ratelimit = False ++ while awaiting_ratelimit: ++ can_do_action, ratelimit_expiry = await ratelimiter.can_do_action(requester, update=False) ++ if not can_do_action: ++ # can_do_action returns an absolute timestamp, convert it to a relative time ++ time_to_sleep = ratelimit_expiry - ratelimiter.clock.time() ++ logger.warning("bulk_send_events: Got rate limited in bulk sending events, waiting %ds", time_to_sleep) ++ await ratelimiter.clock.sleep(time_to_sleep) ++ ratelimit_hit = True ++ else: ++ awaiting_ratelimit = False ++ await ratelimiter.can_do_action(requester, update=True) ++ return ratelimit_hit ++ ++ async def on_POST( ++ self, request: SynapseRequest, room_identifier: str ++ ) -> tuple[int, JsonDict]: ++ logger.warning("bulk_send_events: Got bulk send events request") ++ requester = await self._auth.get_user_by_req(request, allow_guest=False) ++ room_id, remote_room_hosts = await self.resolve_room_id(room_identifier) ++ ++ #force_sync_interval = parse_integer(request, "force_sync_interval", default=250) ++ ++ events = ijson.items( ++ request.content, ++ "item" ++ ) ++ ++ i = 0 ++ queued_event_dicts = [] ++ ++ for event_data in events: ++ current_index = i ++ i += 1 ++ current_time_ms = self._event_creation_handler.request_ratelimiter.clock.time_msec() ++ logger.info("bulk_send_events: Processing event %d: %s", current_index, event_data) ++ ++ event_dict: JsonDict = { ++ "type": event_data.get("type"), ++ "content": event_data.get("content", {}), ++ "room_id": room_id, ++ "sender": requester.user.to_string(), ++ } ++ ++ if "state_key" in event_data: ++ event_dict["state_key"] = event_data["state_key"] ++ ++ queued_event_dicts.append(event_dict) ++ # Explicitly handle rate limits in order to avoid compounding effects ++ ratelimit_hit = False # await self.wait_ratelimit(self._event_creation_handler.request_ratelimiter, requester) ++ ++ #if ratelimit_hit: ++ # logger.warning("bulk_send_events: Rate limit hit after %d events", i) ++ # await self._event_creation_handler.create_and_send_new_client_events( ++ # requester, ++ # room_id, ++ # prev_event_id, ++ # event_dicts=queued_event_dicts, ++ # ratelimit=False, ++ # ignore_shadow_ban=True #TODO: remove ++ # ) ++ # #logger.warning("bulk_send_events: ", i) ++ # queued_event_dicts = [] ++ ++ latest_events: list = await self._storage_controllers.main.get_latest_event_ids_in_room(room_id) ++ prev_event_id = list(latest_events).pop() ++ await self._event_creation_handler.create_and_send_new_client_events( ++ requester, ++ room_id, ++ prev_event_id, ++ event_dicts=queued_event_dicts, ++ ratelimit=False, ++ ignore_shadow_ban=True #TODO: remove ++ ) ++ logger.warning("bulk_send_events: Finished processing %d events in %d ms", i, self._event_creation_handler.request_ratelimiter.clock.time_msec() - current_time_ms) ++ ++ return 200, {} ++ + + def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: + RoomStateEventRestServlet(hs).register(http_server) +@@ -1764,6 +1873,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: + JoinRoomAliasServlet(hs).register(http_server) + RoomMembershipRestServlet(hs).register(http_server) + RoomSendEventRestServlet(hs).register(http_server) ++ RoomBulkSendEventRestServlet(hs).register(http_server) + PublicRoomListRestServlet(hs).register(http_server) + RoomStateRestServlet(hs).register(http_server) + RoomRedactEventRestServlet(hs).register(http_server) +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0016-Clarify-pre_event_ids-assert-in-event-creation-handl.patch b/packages/overlays/matrix-synapse/patches/0016-Clarify-pre_event_ids-assert-in-event-creation-handl.patch deleted file mode 100644 index 3c947d6..0000000 --- a/packages/overlays/matrix-synapse/patches/0016-Clarify-pre_event_ids-assert-in-event-creation-handl.patch +++ /dev/null @@ -1,26 +0,0 @@ -From cebfa78c89bd9a89b652fce64dc99fe26df1b6b1 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Sat, 16 Aug 2025 20:19:08 +0200 -Subject: [PATCH 16/25] Clarify pre_event_ids assert in event creation handler - -Signed-off-by: Rory& ---- - synapse/handlers/message.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py -index eb01622515..65782dc459 100644 ---- a/synapse/handlers/message.py -+++ b/synapse/handlers/message.py -@@ -1285,7 +1285,7 @@ class EventCreationHandler: - if state_event_ids is not None: - # Do a quick check to make sure that prev_event_ids is present to - # make the type-checking around `builder.build` happy. -- assert prev_event_ids is not None -+ assert prev_event_ids is not None, "create_new_client_event called with state_event_ids but no prev_event_ids" - - temp_event = await builder.build( - prev_event_ids=prev_event_ids, --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch b/packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch deleted file mode 100644 index 7941117..0000000 --- a/packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch +++ /dev/null @@ -1,196 +0,0 @@ -From ac2d85fc125811f2d6d9f302ed30e2f96fc4491e Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Sat, 26 Jul 2025 09:50:56 +0200 -Subject: [PATCH 17/25] Add bulk send events endpoint - -Try to optimise bulk sending some more - -Further optimisation attempts - -Use create_and_send_new_client_events for bulk sending - -Signed-off-by: Rory& ---- - synapse/rest/client/capabilities.py | 3 + - synapse/rest/client/room.py | 112 +++++++++++++++++++++++++++- - 2 files changed, 114 insertions(+), 1 deletion(-) - -diff --git a/synapse/rest/client/capabilities.py b/synapse/rest/client/capabilities.py -index 705d74dee1..075c3de261 100644 ---- a/synapse/rest/client/capabilities.py -+++ b/synapse/rest/client/capabilities.py -@@ -74,6 +74,9 @@ class CapabilitiesRestServlet(RestServlet): - "m.get_login_token": { - "enabled": self.config.auth.login_via_existing_enabled, - }, -+ "gay.rory.bulk_send_events": { -+ "enabled": True -+ } - } - } - -diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py -index 875890470e..67820f706e 100644 ---- a/synapse/rest/client/room.py -+++ b/synapse/rest/client/room.py -@@ -23,10 +23,12 @@ - - import logging - import re -+import ijson - from enum import Enum - from http import HTTPStatus - from typing import TYPE_CHECKING, Awaitable - from urllib import parse as urlparse -+from twisted.internet import defer - - import attr - from prometheus_client.core import Histogram -@@ -51,6 +53,7 @@ from synapse.api.errors import ( - UnredactedContentDeletedError, - ) - from synapse.api.filtering import Filter -+from synapse.api.ratelimiting import RequestRatelimiter - from synapse.events.utils import ( - EventClientSerializer, - SerializeEventConfig, -@@ -58,6 +61,7 @@ from synapse.events.utils import ( - serialize_event, - ) - from synapse.handlers.pagination import GetMessagesResult -+from synapse.events import EventBase - from synapse.http.server import HttpServer - from synapse.http.servlet import ( - ResolveRoomIdMixin, -@@ -513,7 +517,6 @@ class RoomSendEventRestServlet(TransactionRestServlet): - txn_id, - ) - -- - def _parse_request_delay( - request: SynapseRequest, - max_delay: int | None, -@@ -1755,6 +1758,112 @@ class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet): - remote_room_hosts, - ) - -+class RoomBulkSendEventRestServlet(ResolveRoomIdMixin, RestServlet): -+ """ -+ Bulk send events to a room. -+ -+ This endpoint allows sending multiple events to a room in a single request, -+ avoiding event linearisation issues. -+ """ -+ -+ PATTERNS = ( -+ re.compile( -+ "^/_matrix/client/unstable/gay.rory.bulk_send_events" -+ "/rooms/(?P[^/]*)/bulk_send_events$" -+ ), -+ ) -+ CATEGORY = "Event sending requests" -+ -+ def __init__(self, hs: "HomeServer"): -+ super().__init__(hs) -+ self._auth = hs.get_auth() -+ self._event_creation_handler = hs.get_event_creation_handler() -+ self._message_handler = hs.get_message_handler() -+ self._storage_controllers = hs.get_storage_controllers() -+ -+ async def wait_ratelimit(ratelimiter: RequestRatelimiter, requester: Requester) -> bool: -+ """ -+ Wait until the ratelimiter allows the requester to proceed. -+ """ -+ ratelimit_hit = False -+ awaiting_ratelimit = False -+ while awaiting_ratelimit: -+ can_do_action, ratelimit_expiry = await ratelimiter.can_do_action(requester, update=False) -+ if not can_do_action: -+ # can_do_action returns an absolute timestamp, convert it to a relative time -+ time_to_sleep = ratelimit_expiry - ratelimiter.clock.time() -+ logger.warning("bulk_send_events: Got rate limited in bulk sending events, waiting %ds", time_to_sleep) -+ await ratelimiter.clock.sleep(time_to_sleep) -+ ratelimit_hit = True -+ else: -+ awaiting_ratelimit = False -+ await ratelimiter.can_do_action(requester, update=True) -+ return ratelimit_hit -+ -+ async def on_POST( -+ self, request: SynapseRequest, room_identifier: str -+ ) -> tuple[int, JsonDict]: -+ logger.warning("bulk_send_events: Got bulk send events request") -+ requester = await self._auth.get_user_by_req(request, allow_guest=False) -+ room_id, remote_room_hosts = await self.resolve_room_id(room_identifier) -+ -+ #force_sync_interval = parse_integer(request, "force_sync_interval", default=250) -+ -+ events = ijson.items( -+ request.content, -+ "item" -+ ) -+ -+ i = 0 -+ queued_event_dicts = [] -+ -+ for event_data in events: -+ current_index = i -+ i += 1 -+ current_time_ms = self._event_creation_handler.request_ratelimiter.clock.time_msec() -+ logger.info("bulk_send_events: Processing event %d: %s", current_index, event_data) -+ -+ event_dict: JsonDict = { -+ "type": event_data.get("type"), -+ "content": event_data.get("content", {}), -+ "room_id": room_id, -+ "sender": requester.user.to_string(), -+ } -+ -+ if "state_key" in event_data: -+ event_dict["state_key"] = event_data["state_key"] -+ -+ queued_event_dicts.append(event_dict) -+ # Explicitly handle rate limits in order to avoid compounding effects -+ ratelimit_hit = False # await self.wait_ratelimit(self._event_creation_handler.request_ratelimiter, requester) -+ -+ #if ratelimit_hit: -+ # logger.warning("bulk_send_events: Rate limit hit after %d events", i) -+ # await self._event_creation_handler.create_and_send_new_client_events( -+ # requester, -+ # room_id, -+ # prev_event_id, -+ # event_dicts=queued_event_dicts, -+ # ratelimit=False, -+ # ignore_shadow_ban=True #TODO: remove -+ # ) -+ # #logger.warning("bulk_send_events: ", i) -+ # queued_event_dicts = [] -+ -+ latest_events: list = await self._storage_controllers.main.get_latest_event_ids_in_room(room_id) -+ prev_event_id = list(latest_events).pop() -+ await self._event_creation_handler.create_and_send_new_client_events( -+ requester, -+ room_id, -+ prev_event_id, -+ event_dicts=queued_event_dicts, -+ ratelimit=False, -+ ignore_shadow_ban=True #TODO: remove -+ ) -+ logger.warning("bulk_send_events: Finished processing %d events in %d ms", i, self._event_creation_handler.request_ratelimiter.clock.time_msec() - current_time_ms) -+ -+ return 200, {} -+ - - def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - RoomStateEventRestServlet(hs).register(http_server) -@@ -1764,6 +1873,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: - JoinRoomAliasServlet(hs).register(http_server) - RoomMembershipRestServlet(hs).register(http_server) - RoomSendEventRestServlet(hs).register(http_server) -+ RoomBulkSendEventRestServlet(hs).register(http_server) - PublicRoomListRestServlet(hs).register(http_server) - RoomStateRestServlet(hs).register(http_server) - RoomRedactEventRestServlet(hs).register(http_server) --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0017-admin-api-send-more-data.patch b/packages/overlays/matrix-synapse/patches/0017-admin-api-send-more-data.patch new file mode 100644 index 0000000..4f56fff --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0017-admin-api-send-more-data.patch @@ -0,0 +1,170 @@ +From e72191197df3a6570dcf01ec4388eff15157ab58 Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Mon, 27 Oct 2025 19:23:42 +0100 +Subject: [PATCH 17/24] admin api - send more data + +Signed-off-by: Rory& +--- + synapse/rest/admin/rooms.py | 14 ++++- + synapse/rest/client/capabilities.py | 3 ++ + synapse/storage/databases/main/room.py | 71 ++++++++++++++++++-------- + 3 files changed, 66 insertions(+), 22 deletions(-) + +diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py +index e97d076a44..09a8a01f77 100644 +--- a/synapse/rest/admin/rooms.py ++++ b/synapse/rest/admin/rooms.py +@@ -302,7 +302,15 @@ class ListRoomRestServlet(RestServlet): + reverse_order = True if direction == Direction.BACKWARDS else False + + emma_include_tombstone = parse_boolean( +- request, "emma_include_tombstone", default=False ++ request, "gay.rory.synapse_admin_extensions.include_tombstone", default=parse_boolean( ++ request, "emma_include_tombstone", default=False ++ ) ++ ) ++ emma_include_topic = parse_boolean( ++ request, "gay.rory.synapse_admin_extensions.include_topic", default=False ++ ) ++ emma_include_create_evt = parse_boolean( ++ request, "gay.rory.synapse_admin_extensions.include_create_event", default=False + ) + + # Return list of rooms according to parameters +@@ -314,7 +322,9 @@ class ListRoomRestServlet(RestServlet): + search_term, + public_rooms, + empty_rooms, +- emma_include_tombstone = emma_include_tombstone ++ emma_include_tombstone, ++ emma_include_topic, ++ emma_include_create_evt + ) + + response = { +diff --git a/synapse/rest/client/capabilities.py b/synapse/rest/client/capabilities.py +index 075c3de261..c0b0a9923f 100644 +--- a/synapse/rest/client/capabilities.py ++++ b/synapse/rest/client/capabilities.py +@@ -76,6 +76,9 @@ class CapabilitiesRestServlet(RestServlet): + }, + "gay.rory.bulk_send_events": { + "enabled": True ++ }, ++ "gay.rory.synapse_admin_extensions.room_list.query_events.v2": { ++ "enabled": True + } + } + } +diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py +index ff647c0c5b..823540f4a0 100644 +--- a/synapse/storage/databases/main/room.py ++++ b/synapse/storage/databases/main/room.py +@@ -606,6 +606,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + public_rooms: bool | None, + empty_rooms: bool | None, + emma_include_tombstone: bool = False, ++ emma_include_topic: bool = False, ++ emma_include_create_evt: bool = False, + ) -> tuple[list[dict[str, Any]], int]: + """Function to retrieve a paginated list of rooms as json. + +@@ -626,10 +628,13 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + if false, empty rooms are excluded from the query. When it is + none (the default), both empty rooms and none-empty rooms are queried. + emma_include_tombstone: If true, include tombstone events in the results. ++ emma_include_topic: If true, include topic events in the results. ++ emma_include_create_evt: If true, include create events in the results. + Returns: + A list of room dicts and an integer representing the total number of + rooms that exist given this query + """ ++ uses_emma_features = emma_include_tombstone or emma_include_topic or emma_include_create_evt + # Filter room names by a string + filter_ = [] + where_args = [] +@@ -799,35 +804,61 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): + _get_rooms_paginate_txn, + ) + +- if emma_include_tombstone: ++ if uses_emma_features: + room_id_sql, room_id_args = make_in_list_sql_clause( + self.database_engine, "cse.room_id", [r["room_id"] for r in result[0]] + ) + +- tombstone_sql = """ ++ current_state_evt_sql = """ + SELECT cse.room_id, cse.event_id, ej.json +- FROM current_state_events cse +- JOIN event_json ej USING (event_id) +- WHERE cse.type = 'm.room.tombstone' +- AND {room_id_sql} ++ FROM current_state_events cse ++ JOIN event_json ej USING (event_id) ++ WHERE cse.type = '{type_sql}' ++ AND {room_id_sql} + """.format( ++ type_sql="{type_sql}", + room_id_sql=room_id_sql + ) + +- def _get_tombstones_txn( +- txn: LoggingTransaction, +- ) -> Dict[str, JsonDict]: +- txn.execute(tombstone_sql, room_id_args) +- for room_id, event_id, json in txn: +- for result_room in result[0]: +- if result_room["room_id"] == room_id: +- result_room["gay.rory.synapse_admin_extensions.tombstone"] = db_to_json(json) +- break +- return result[0], result[1] +- +- result = await self.db_pool.runInteraction( +- "get_rooms_tombstones", _get_tombstones_txn, +- ) ++ async def include_current_state_txn( ++ desc: str, event_type: str, result_key: str ++ ): ++ def _include_current_state_txn( ++ txn: LoggingTransaction, ++ ) -> dict[str, JsonDict]: ++ sql = current_state_evt_sql.format(type_sql=event_type) ++ logger.warning("emma %s: sql=%s args=%s", desc, sql, room_id_args) ++ txn.execute(sql, room_id_args) ++ current_state_matches = 0 ++ # logger.warning("emma %s: result=%s", desc, txn.fetchall()) ++ for room_id, event_id, json in txn: ++ for result_room in result[0]: ++ if result_room["room_id"] == room_id: ++ result_room[result_key] = db_to_json(json) ++ logger.warning("emma _include_current_state_txn[%s] %s/%s -> %s", current_state_matches, room_id, event_type, event_id) ++ current_state_matches += 1 ++ break ++ return result[0], result[1] ++ ++ return await self.db_pool.runInteraction( ++ desc, _include_current_state_txn, ++ ) ++ ++ if emma_include_tombstone: ++ result = await include_current_state_txn( ++ "get_rooms_tombstones", EventTypes.Tombstone, "gay.rory.synapse_admin_extensions.tombstone" ++ ) ++ logger.warning("emma_include_tombstone result: %s", result) ++ if emma_include_topic: ++ result = await include_current_state_txn( ++ "get_rooms_topics", EventTypes.Topic, "gay.rory.synapse_admin_extensions.room_topic" ++ ) ++ logger.warning("emma_include_topic result: %s", result) ++ if emma_include_create_evt: ++ result = await include_current_state_txn( ++ "get_rooms_create_evts", EventTypes.Create, "gay.rory.synapse_admin_extensions.create_event" ++ ) ++ logger.warning("emma_include_create_evt result: %s", result) + + return result + +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0018-Allow-overriding-max-background-task-count.patch b/packages/overlays/matrix-synapse/patches/0018-Allow-overriding-max-background-task-count.patch new file mode 100644 index 0000000..0c70f7b --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0018-Allow-overriding-max-background-task-count.patch @@ -0,0 +1,52 @@ +From d06812d14a01684c9dfaddc56756118a14e0cdb6 Mon Sep 17 00:00:00 2001 +From: Rory& +Date: Tue, 28 Oct 2025 00:01:45 +0100 +Subject: [PATCH 18/24] Allow overriding max background task count + +Signed-off-by: Rory& +--- + get_sched_tasks.sql | 1 + + synapse/config/ratelimiting.py | 1 + + synapse/util/task_scheduler.py | 6 ++++++ + 3 files changed, 8 insertions(+) + create mode 100644 get_sched_tasks.sql + +diff --git a/get_sched_tasks.sql b/get_sched_tasks.sql +new file mode 100644 +index 0000000000..680342cfa2 +--- /dev/null ++++ b/get_sched_tasks.sql +@@ -0,0 +1 @@ ++select * from scheduled_tasks where status != 'complete'; +diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py +index 13c9c4dba0..a8add434d2 100644 +--- a/synapse/config/ratelimiting.py ++++ b/synapse/config/ratelimiting.py +@@ -84,6 +84,7 @@ class RatelimitConfig(Config): + section = "ratelimiting" + + def read_config(self, config: JsonDict, **kwargs: Any) -> None: ++ self.override_max_concurrent_running_tasks = config.get("ratelimiting", {}).get("override_max_concurrent_running_tasks", None) + # Load the new-style messages config if it exists. Otherwise fall back + # to the old method. + if "rc_message" in config: +diff --git a/synapse/util/task_scheduler.py b/synapse/util/task_scheduler.py +index c1790fd3ae..a083e5b694 100644 +--- a/synapse/util/task_scheduler.py ++++ b/synapse/util/task_scheduler.py +@@ -142,6 +142,12 @@ class TaskScheduler: + hook=lambda: {(self.server_name,): len(self._running_tasks)}, + ) + ++ if hs.config.ratelimiting.override_max_concurrent_running_tasks is not None: ++ TaskScheduler.MAX_CONCURRENT_RUNNING_TASKS = ( ++ hs.config.ratelimiting.override_max_concurrent_running_tasks ++ ) ++ logger.warning("Max concurrent running tasks: %s, override: %s", TaskScheduler.MAX_CONCURRENT_RUNNING_TASKS, hs.config.ratelimiting.override_max_concurrent_running_tasks) ++ + def register_action( + self, + function: Callable[ +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch b/packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch deleted file mode 100644 index 08f1357..0000000 --- a/packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch +++ /dev/null @@ -1,170 +0,0 @@ -From 3196bce10b7842aa435036105d525e0f10153ad9 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Mon, 27 Oct 2025 19:23:42 +0100 -Subject: [PATCH 18/25] admin api - send more data - -Signed-off-by: Rory& ---- - synapse/rest/admin/rooms.py | 14 ++++- - synapse/rest/client/capabilities.py | 3 ++ - synapse/storage/databases/main/room.py | 71 ++++++++++++++++++-------- - 3 files changed, 66 insertions(+), 22 deletions(-) - -diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py -index e97d076a44..09a8a01f77 100644 ---- a/synapse/rest/admin/rooms.py -+++ b/synapse/rest/admin/rooms.py -@@ -302,7 +302,15 @@ class ListRoomRestServlet(RestServlet): - reverse_order = True if direction == Direction.BACKWARDS else False - - emma_include_tombstone = parse_boolean( -- request, "emma_include_tombstone", default=False -+ request, "gay.rory.synapse_admin_extensions.include_tombstone", default=parse_boolean( -+ request, "emma_include_tombstone", default=False -+ ) -+ ) -+ emma_include_topic = parse_boolean( -+ request, "gay.rory.synapse_admin_extensions.include_topic", default=False -+ ) -+ emma_include_create_evt = parse_boolean( -+ request, "gay.rory.synapse_admin_extensions.include_create_event", default=False - ) - - # Return list of rooms according to parameters -@@ -314,7 +322,9 @@ class ListRoomRestServlet(RestServlet): - search_term, - public_rooms, - empty_rooms, -- emma_include_tombstone = emma_include_tombstone -+ emma_include_tombstone, -+ emma_include_topic, -+ emma_include_create_evt - ) - - response = { -diff --git a/synapse/rest/client/capabilities.py b/synapse/rest/client/capabilities.py -index 075c3de261..c0b0a9923f 100644 ---- a/synapse/rest/client/capabilities.py -+++ b/synapse/rest/client/capabilities.py -@@ -76,6 +76,9 @@ class CapabilitiesRestServlet(RestServlet): - }, - "gay.rory.bulk_send_events": { - "enabled": True -+ }, -+ "gay.rory.synapse_admin_extensions.room_list.query_events.v2": { -+ "enabled": True - } - } - } -diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py -index 7623208c28..08735e1fbb 100644 ---- a/synapse/storage/databases/main/room.py -+++ b/synapse/storage/databases/main/room.py -@@ -606,6 +606,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - public_rooms: bool | None, - empty_rooms: bool | None, - emma_include_tombstone: bool = False, -+ emma_include_topic: bool = False, -+ emma_include_create_evt: bool = False, - ) -> tuple[list[dict[str, Any]], int]: - """Function to retrieve a paginated list of rooms as json. - -@@ -626,10 +628,13 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - if false, empty rooms are excluded from the query. When it is - none (the default), both empty rooms and none-empty rooms are queried. - emma_include_tombstone: If true, include tombstone events in the results. -+ emma_include_topic: If true, include topic events in the results. -+ emma_include_create_evt: If true, include create events in the results. - Returns: - A list of room dicts and an integer representing the total number of - rooms that exist given this query - """ -+ uses_emma_features = emma_include_tombstone or emma_include_topic or emma_include_create_evt - # Filter room names by a string - filter_ = [] - where_args = [] -@@ -799,35 +804,61 @@ class RoomWorkerStore(CacheInvalidationWorkerStore): - _get_rooms_paginate_txn, - ) - -- if emma_include_tombstone: -+ if uses_emma_features: - room_id_sql, room_id_args = make_in_list_sql_clause( - self.database_engine, "cse.room_id", [r["room_id"] for r in result[0]] - ) - -- tombstone_sql = """ -+ current_state_evt_sql = """ - SELECT cse.room_id, cse.event_id, ej.json -- FROM current_state_events cse -- JOIN event_json ej USING (event_id) -- WHERE cse.type = 'm.room.tombstone' -- AND {room_id_sql} -+ FROM current_state_events cse -+ JOIN event_json ej USING (event_id) -+ WHERE cse.type = '{type_sql}' -+ AND {room_id_sql} - """.format( -+ type_sql="{type_sql}", - room_id_sql=room_id_sql - ) - -- def _get_tombstones_txn( -- txn: LoggingTransaction, -- ) -> Dict[str, JsonDict]: -- txn.execute(tombstone_sql, room_id_args) -- for room_id, event_id, json in txn: -- for result_room in result[0]: -- if result_room["room_id"] == room_id: -- result_room["gay.rory.synapse_admin_extensions.tombstone"] = db_to_json(json) -- break -- return result[0], result[1] -- -- result = await self.db_pool.runInteraction( -- "get_rooms_tombstones", _get_tombstones_txn, -- ) -+ async def include_current_state_txn( -+ desc: str, event_type: str, result_key: str -+ ): -+ def _include_current_state_txn( -+ txn: LoggingTransaction, -+ ) -> dict[str, JsonDict]: -+ sql = current_state_evt_sql.format(type_sql=event_type) -+ logger.warning("emma %s: sql=%s args=%s", desc, sql, room_id_args) -+ txn.execute(sql, room_id_args) -+ current_state_matches = 0 -+ # logger.warning("emma %s: result=%s", desc, txn.fetchall()) -+ for room_id, event_id, json in txn: -+ for result_room in result[0]: -+ if result_room["room_id"] == room_id: -+ result_room[result_key] = db_to_json(json) -+ logger.warning("emma _include_current_state_txn[%s] %s/%s -> %s", current_state_matches, room_id, event_type, event_id) -+ current_state_matches += 1 -+ break -+ return result[0], result[1] -+ -+ return await self.db_pool.runInteraction( -+ desc, _include_current_state_txn, -+ ) -+ -+ if emma_include_tombstone: -+ result = await include_current_state_txn( -+ "get_rooms_tombstones", EventTypes.Tombstone, "gay.rory.synapse_admin_extensions.tombstone" -+ ) -+ logger.warning("emma_include_tombstone result: %s", result) -+ if emma_include_topic: -+ result = await include_current_state_txn( -+ "get_rooms_topics", EventTypes.Topic, "gay.rory.synapse_admin_extensions.room_topic" -+ ) -+ logger.warning("emma_include_topic result: %s", result) -+ if emma_include_create_evt: -+ result = await include_current_state_txn( -+ "get_rooms_create_evts", EventTypes.Create, "gay.rory.synapse_admin_extensions.create_event" -+ ) -+ logger.warning("emma_include_create_evt result: %s", result) - - return result - --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch b/packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch deleted file mode 100644 index ae0fe60..0000000 --- a/packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch +++ /dev/null @@ -1,52 +0,0 @@ -From 4c2eb25d811fd5deeb2c92318460fcd7bdf7dfe1 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 28 Oct 2025 00:01:45 +0100 -Subject: [PATCH 19/25] Allow overriding max background task count - -Signed-off-by: Rory& ---- - get_sched_tasks.sql | 1 + - synapse/config/ratelimiting.py | 1 + - synapse/util/task_scheduler.py | 6 ++++++ - 3 files changed, 8 insertions(+) - create mode 100644 get_sched_tasks.sql - -diff --git a/get_sched_tasks.sql b/get_sched_tasks.sql -new file mode 100644 -index 0000000000..680342cfa2 ---- /dev/null -+++ b/get_sched_tasks.sql -@@ -0,0 +1 @@ -+select * from scheduled_tasks where status != 'complete'; -diff --git a/synapse/config/ratelimiting.py b/synapse/config/ratelimiting.py -index 13c9c4dba0..a8add434d2 100644 ---- a/synapse/config/ratelimiting.py -+++ b/synapse/config/ratelimiting.py -@@ -84,6 +84,7 @@ class RatelimitConfig(Config): - section = "ratelimiting" - - def read_config(self, config: JsonDict, **kwargs: Any) -> None: -+ self.override_max_concurrent_running_tasks = config.get("ratelimiting", {}).get("override_max_concurrent_running_tasks", None) - # Load the new-style messages config if it exists. Otherwise fall back - # to the old method. - if "rc_message" in config: -diff --git a/synapse/util/task_scheduler.py b/synapse/util/task_scheduler.py -index c1790fd3ae..a083e5b694 100644 ---- a/synapse/util/task_scheduler.py -+++ b/synapse/util/task_scheduler.py -@@ -142,6 +142,12 @@ class TaskScheduler: - hook=lambda: {(self.server_name,): len(self._running_tasks)}, - ) - -+ if hs.config.ratelimiting.override_max_concurrent_running_tasks is not None: -+ TaskScheduler.MAX_CONCURRENT_RUNNING_TASKS = ( -+ hs.config.ratelimiting.override_max_concurrent_running_tasks -+ ) -+ logger.warning("Max concurrent running tasks: %s, override: %s", TaskScheduler.MAX_CONCURRENT_RUNNING_TASKS, hs.config.ratelimiting.override_max_concurrent_running_tasks) -+ - def register_action( - self, - function: Callable[ --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0019-max-and-min-were-probably-switched.-Set-max-to-arbit.patch b/packages/overlays/matrix-synapse/patches/0019-max-and-min-were-probably-switched.-Set-max-to-arbit.patch new file mode 100644 index 0000000..7052a97 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0019-max-and-min-were-probably-switched.-Set-max-to-arbit.patch @@ -0,0 +1,39 @@ +From 9278458858ba5237de2def33e479257da847ef8b Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Fri, 2 Jan 2026 12:48:22 -0600 +Subject: [PATCH 19/24] max() and min() were probably switched. Set max to + arbitrary 15 minutes, continue logging at durations greater than 10 minutes + +--- + synapse/handlers/worker_lock.py | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/synapse/handlers/worker_lock.py b/synapse/handlers/worker_lock.py +index 1537a18cc0..82dd896d5a 100644 +--- a/synapse/handlers/worker_lock.py ++++ b/synapse/handlers/worker_lock.py +@@ -275,8 +275,8 @@ class WaitingLock: + + def _get_next_retry_interval(self) -> float: + next = self._retry_interval +- self._retry_interval = max(5, next * 2) +- if self._retry_interval > Duration(minutes=10).as_secs(): # >7 iterations ++ self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) ++ if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations + logger.warning( + "Lock timeout is getting excessive: %ss. There may be a deadlock.", + self._retry_interval, +@@ -362,8 +362,8 @@ class WaitingMultiLock: + + def _get_next_retry_interval(self) -> float: + next = self._retry_interval +- self._retry_interval = max(5, next * 2) +- if self._retry_interval > Duration(minutes=10).as_secs(): # >7 iterations ++ self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) ++ if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations + logger.warning( + "Lock timeout is getting excessive: %ss. There may be a deadlock.", + self._retry_interval, +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0020-changelog.patch b/packages/overlays/matrix-synapse/patches/0020-changelog.patch new file mode 100644 index 0000000..28df036 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0020-changelog.patch @@ -0,0 +1,20 @@ +From f519fa835fa3da23ed312d7c64e548635dce28a5 Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Tue, 20 Jan 2026 06:42:18 -0600 +Subject: [PATCH 20/24] changelog + +--- + changelog.d/19394.bugfix | 1 + + 1 file changed, 1 insertion(+) + create mode 100644 changelog.d/19394.bugfix + +diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix +new file mode 100644 +index 0000000000..eb93fffe15 +--- /dev/null ++++ b/changelog.d/19394.bugfix +@@ -0,0 +1 @@ ++Prevent excessively long numbers for the retry interval of `WorkerLock`s. Contributed by Famedly. +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch b/packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch deleted file mode 100644 index 2eb65d0..0000000 --- a/packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 1234193eed9a92a21847d69b59e1673fdf43e0d6 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Fri, 2 Jan 2026 12:48:22 -0600 -Subject: [PATCH 20/25] max() and min() were probably switched. Set max to - arbitrary 15 minutes, continue logging at durations greater than 10 minutes - ---- - synapse/handlers/worker_lock.py | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/synapse/handlers/worker_lock.py b/synapse/handlers/worker_lock.py -index 1537a18cc0..82dd896d5a 100644 ---- a/synapse/handlers/worker_lock.py -+++ b/synapse/handlers/worker_lock.py -@@ -275,8 +275,8 @@ class WaitingLock: - - def _get_next_retry_interval(self) -> float: - next = self._retry_interval -- self._retry_interval = max(5, next * 2) -- if self._retry_interval > Duration(minutes=10).as_secs(): # >7 iterations -+ self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) -+ if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations - logger.warning( - "Lock timeout is getting excessive: %ss. There may be a deadlock.", - self._retry_interval, -@@ -362,8 +362,8 @@ class WaitingMultiLock: - - def _get_next_retry_interval(self) -> float: - next = self._retry_interval -- self._retry_interval = max(5, next * 2) -- if self._retry_interval > Duration(minutes=10).as_secs(): # >7 iterations -+ self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) -+ if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations - logger.warning( - "Lock timeout is getting excessive: %ss. There may be a deadlock.", - self._retry_interval, --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0021-Update-changelog.d-19394.bugfix.patch b/packages/overlays/matrix-synapse/patches/0021-Update-changelog.d-19394.bugfix.patch new file mode 100644 index 0000000..b6a0441 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0021-Update-changelog.d-19394.bugfix.patch @@ -0,0 +1,20 @@ +From 351c195c6e78f93f22d01c368163e9db94e7496d Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Wed, 21 Jan 2026 06:57:34 -0600 +Subject: [PATCH 21/24] Update changelog.d/19394.bugfix + +Co-authored-by: Eric Eastwood +--- + changelog.d/19394.bugfix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix +index eb93fffe15..3591c0a745 100644 +--- a/changelog.d/19394.bugfix ++++ b/changelog.d/19394.bugfix +@@ -1 +1 @@ +-Prevent excessively long numbers for the retry interval of `WorkerLock`s. Contributed by Famedly. ++Capped the `WorkerLock` retry interval to a maximum of 15 minutes to prevent dealing with excessively long numbers. Contributed by Famedly. +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0021-changelog.patch b/packages/overlays/matrix-synapse/patches/0021-changelog.patch deleted file mode 100644 index 080adb5..0000000 --- a/packages/overlays/matrix-synapse/patches/0021-changelog.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 2f79297e093ae244cb04384dd5f82b22f1ab4368 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Tue, 20 Jan 2026 06:42:18 -0600 -Subject: [PATCH 21/25] changelog - ---- - changelog.d/19394.bugfix | 1 + - 1 file changed, 1 insertion(+) - create mode 100644 changelog.d/19394.bugfix - -diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix -new file mode 100644 -index 0000000000..eb93fffe15 ---- /dev/null -+++ b/changelog.d/19394.bugfix -@@ -0,0 +1 @@ -+Prevent excessively long numbers for the retry interval of `WorkerLock`s. Contributed by Famedly. --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0022-Adjust-for-the-retry-interval-actually-being-a-timeo.patch b/packages/overlays/matrix-synapse/patches/0022-Adjust-for-the-retry-interval-actually-being-a-timeo.patch new file mode 100644 index 0000000..bcfbdf4 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0022-Adjust-for-the-retry-interval-actually-being-a-timeo.patch @@ -0,0 +1,257 @@ +From d4b741a582dde5ab68bbda75e02f617758055a52 Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Mon, 26 Jan 2026 10:26:38 -0600 +Subject: [PATCH 22/24] Adjust for the retry interval actually being a timeout + interval, and only increase it when a timeout occurs + +--- + synapse/handlers/worker_lock.py | 56 +++++++++-------- + tests/handlers/test_worker_lock.py | 99 ++++++++++++++++++++++++++---- + 2 files changed, 120 insertions(+), 35 deletions(-) + +diff --git a/synapse/handlers/worker_lock.py b/synapse/handlers/worker_lock.py +index 82dd896d5a..88ecfd6318 100644 +--- a/synapse/handlers/worker_lock.py ++++ b/synapse/handlers/worker_lock.py +@@ -208,7 +208,7 @@ class WaitingLock: + write: bool | None + deferred: "defer.Deferred[None]" = attr.Factory(defer.Deferred) + _inner_lock: Lock | None = None +- _retry_interval: float = 0.1 ++ _timeout_interval: float = 0.1 + _lock_span: "opentracing.Scope" = attr.Factory( + lambda: start_active_span("WaitingLock.lock") + ) +@@ -240,19 +240,23 @@ class WaitingLock: + break + + try: +- # Wait until the we get notified the lock might have been ++ # Wait until the notification the lock might have been + # released (by the deferred being resolved). We also +- # periodically wake up in case the lock was released but we ++ # periodically wake up in case the lock was released, but we + # weren't notified. + with PreserveLoggingContext(): +- timeout = self._get_next_retry_interval() + await timeout_deferred( + deferred=self.deferred, +- timeout=timeout, ++ timeout=self._timeout_interval, + clock=self.clock, + ) +- except Exception: +- pass ++ except defer.TimeoutError: ++ # Only increment the timeout interval if this was an actual timeout ++ self._timeout_interval = self._increment_timeout_interval() ++ except Exception as e: ++ logger.warning( ++ "Caught an exception while waiting on WaitingLock: %r", e ++ ) + + return await self._inner_lock.__aenter__() + +@@ -273,13 +277,13 @@ class WaitingLock: + + return r + +- def _get_next_retry_interval(self) -> float: +- next = self._retry_interval +- self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) +- if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations ++ def _increment_timeout_interval(self) -> float: ++ next = self._timeout_interval ++ next = min(Duration(minutes=15).as_secs(), next * 2) ++ if next > Duration(minutes=10).as_secs(): # >12 iterations + logger.warning( + "Lock timeout is getting excessive: %ss. There may be a deadlock.", +- self._retry_interval, ++ next, + ) + return next * random.uniform(0.9, 1.1) + +@@ -297,7 +301,7 @@ class WaitingMultiLock: + deferred: "defer.Deferred[None]" = attr.Factory(defer.Deferred) + + _inner_lock_cm: AsyncContextManager | None = None +- _retry_interval: float = 0.1 ++ _timeout_interval: float = 0.1 + _lock_span: "opentracing.Scope" = attr.Factory( + lambda: start_active_span("WaitingLock.lock") + ) +@@ -324,19 +328,23 @@ class WaitingMultiLock: + break + + try: +- # Wait until the we get notified the lock might have been ++ # Wait until the notification the lock might have been + # released (by the deferred being resolved). We also +- # periodically wake up in case the lock was released but we ++ # periodically wake up in case the lock was released, but we + # weren't notified. + with PreserveLoggingContext(): +- timeout = self._get_next_retry_interval() + await timeout_deferred( + deferred=self.deferred, +- timeout=timeout, ++ timeout=self._timeout_interval, + clock=self.clock, + ) +- except Exception: +- pass ++ except defer.TimeoutError: ++ # Only increment the timeout interval if this was an actual timeout ++ self._timeout_interval = self._increment_timeout_interval() ++ except Exception as e: ++ logger.warning( ++ "Caught an exception while waiting on WaitingMultiLock: %r", e ++ ) + + assert self._inner_lock_cm + await self._inner_lock_cm.__aenter__() +@@ -360,12 +368,12 @@ class WaitingMultiLock: + + return r + +- def _get_next_retry_interval(self) -> float: +- next = self._retry_interval +- self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) +- if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations ++ def _increment_timeout_interval(self) -> float: ++ next = self._timeout_interval ++ next = min(Duration(minutes=15).as_secs(), next * 2) ++ if next > Duration(minutes=10).as_secs(): # >12 iterations + logger.warning( + "Lock timeout is getting excessive: %ss. There may be a deadlock.", +- self._retry_interval, ++ next, + ) + return next * random.uniform(0.9, 1.1) +diff --git a/tests/handlers/test_worker_lock.py b/tests/handlers/test_worker_lock.py +index 61ff51ff92..3ae44d48e2 100644 +--- a/tests/handlers/test_worker_lock.py ++++ b/tests/handlers/test_worker_lock.py +@@ -21,6 +21,7 @@ + + import logging + import platform ++from unittest.mock import patch + + from twisted.internet import defer + from twisted.internet.testing import MemoryReactor +@@ -48,13 +49,47 @@ class WorkerLockTestCase(unittest.HomeserverTestCase): + self.get_success(lock1.__aenter__()) + + lock2 = self.worker_lock_handler.acquire_lock("name", "key") +- d2 = defer.ensureDeferred(lock2.__aenter__()) +- self.assertNoResult(d2) +- +- self.get_success(lock1.__aexit__(None, None, None)) ++ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit ++ with patch.object( ++ lock2, ++ "_increment_timeout_interval", ++ wraps=lock2._increment_timeout_interval, ++ ) as wrapped_lock2_increment_timeout_interval_method: ++ d2 = defer.ensureDeferred(lock2.__aenter__()) ++ self.assertNoResult(d2) ++ ++ # The lock should not time out here ++ wrapped_lock2_increment_timeout_interval_method.assert_not_called() ++ self.get_success(lock1.__aexit__(None, None, None)) ++ ++ self.get_success(d2) ++ self.get_success(lock2.__aexit__(None, None, None)) ++ ++ def test_timeouts_for_lock_locally(self) -> None: ++ """Test timeouts are incremented for a lock on a single worker""" ++ lock1 = self.worker_lock_handler.acquire_lock("name", "key") ++ self.get_success(lock1.__aenter__()) + +- self.get_success(d2) +- self.get_success(lock2.__aexit__(None, None, None)) ++ lock2 = self.worker_lock_handler.acquire_lock("name", "key") ++ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit ++ with patch.object( ++ lock2, ++ "_increment_timeout_interval", ++ wraps=lock2._increment_timeout_interval, ++ ) as wrapped_lock2_increment_timeout_interval_method: ++ d2 = defer.ensureDeferred(lock2.__aenter__()) ++ self.assertNoResult(d2) ++ ++ # Recall that pump() will advance time of the given amount 100 times, this ++ # amounts to about 10 seconds passing ++ self.pump(10.0) ++ ++ # Should be timed out 6 times, but do not fail on that exact count ++ wrapped_lock2_increment_timeout_interval_method.assert_called() ++ self.get_success(lock1.__aexit__(None, None, None)) ++ ++ self.get_success(d2) ++ self.get_success(lock2.__aexit__(None, None, None)) + + def test_lock_contention(self) -> None: + """Test lock contention when a lot of locks wait on a single worker""" +@@ -117,10 +152,52 @@ class WorkerLockWorkersTestCase(BaseMultiWorkerStreamTestCase): + self.get_success(lock1.__aenter__()) + + lock2 = worker_lock_handler.acquire_lock("name", "key") +- d2 = defer.ensureDeferred(lock2.__aenter__()) +- self.assertNoResult(d2) ++ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit ++ with patch.object( ++ lock2, ++ "_increment_timeout_interval", ++ wraps=lock2._increment_timeout_interval, ++ ) as wrapped_lock2_increment_timeout_interval_method: ++ d2 = defer.ensureDeferred(lock2.__aenter__()) ++ self.assertNoResult(d2) ++ ++ # The lock should not time out here ++ wrapped_lock2_increment_timeout_interval_method.assert_not_called() ++ self.get_success(lock1.__aexit__(None, None, None)) ++ ++ self.get_success(d2) ++ self.get_success(lock2.__aexit__(None, None, None)) ++ ++ def test_timeouts_for_lock_worker(self) -> None: ++ """Test timeouts are incremented for a lock on another worker""" ++ worker = self.make_worker_hs( ++ "synapse.app.generic_worker", ++ extra_config={ ++ "redis": {"enabled": True}, ++ }, ++ ) ++ worker_lock_handler = worker.get_worker_locks_handler() + +- self.get_success(lock1.__aexit__(None, None, None)) ++ lock1 = self.main_worker_lock_handler.acquire_lock("name", "key") ++ self.get_success(lock1.__aenter__()) + +- self.get_success(d2) +- self.get_success(lock2.__aexit__(None, None, None)) ++ lock2 = worker_lock_handler.acquire_lock("name", "key") ++ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit ++ with patch.object( ++ lock2, ++ "_increment_timeout_interval", ++ wraps=lock2._increment_timeout_interval, ++ ) as wrapped_lock2_increment_timeout_interval_method: ++ d2 = defer.ensureDeferred(lock2.__aenter__()) ++ self.assertNoResult(d2) ++ ++ # Recall that pump() will advance time of the given amount 100 times, this ++ # amounts to about 10 seconds passing ++ self.pump(0.1) ++ ++ # Should be timed out 6 times, but do not fail on that exact count ++ wrapped_lock2_increment_timeout_interval_method.assert_called() ++ self.get_success(lock1.__aexit__(None, None, None)) ++ ++ self.get_success(d2) ++ self.get_success(lock2.__aexit__(None, None, None)) +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch b/packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch deleted file mode 100644 index f44d4a6..0000000 --- a/packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch +++ /dev/null @@ -1,20 +0,0 @@ -From a72a358805f6c9be5b4b2145feb68f5ac04d7d76 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Wed, 21 Jan 2026 06:57:34 -0600 -Subject: [PATCH 22/25] Update changelog.d/19394.bugfix - -Co-authored-by: Eric Eastwood ---- - changelog.d/19394.bugfix | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix -index eb93fffe15..3591c0a745 100644 ---- a/changelog.d/19394.bugfix -+++ b/changelog.d/19394.bugfix -@@ -1 +1 @@ --Prevent excessively long numbers for the retry interval of `WorkerLock`s. Contributed by Famedly. -+Capped the `WorkerLock` retry interval to a maximum of 15 minutes to prevent dealing with excessively long numbers. Contributed by Famedly. --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch b/packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch deleted file mode 100644 index a0b4f12..0000000 --- a/packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch +++ /dev/null @@ -1,257 +0,0 @@ -From 1b4f575be3f4607e49422bf5aa25d6c43e19476f Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 10:26:38 -0600 -Subject: [PATCH 23/25] Adjust for the retry interval actually being a timeout - interval, and only increase it when a timeout occurs - ---- - synapse/handlers/worker_lock.py | 56 +++++++++-------- - tests/handlers/test_worker_lock.py | 99 ++++++++++++++++++++++++++---- - 2 files changed, 120 insertions(+), 35 deletions(-) - -diff --git a/synapse/handlers/worker_lock.py b/synapse/handlers/worker_lock.py -index 82dd896d5a..88ecfd6318 100644 ---- a/synapse/handlers/worker_lock.py -+++ b/synapse/handlers/worker_lock.py -@@ -208,7 +208,7 @@ class WaitingLock: - write: bool | None - deferred: "defer.Deferred[None]" = attr.Factory(defer.Deferred) - _inner_lock: Lock | None = None -- _retry_interval: float = 0.1 -+ _timeout_interval: float = 0.1 - _lock_span: "opentracing.Scope" = attr.Factory( - lambda: start_active_span("WaitingLock.lock") - ) -@@ -240,19 +240,23 @@ class WaitingLock: - break - - try: -- # Wait until the we get notified the lock might have been -+ # Wait until the notification the lock might have been - # released (by the deferred being resolved). We also -- # periodically wake up in case the lock was released but we -+ # periodically wake up in case the lock was released, but we - # weren't notified. - with PreserveLoggingContext(): -- timeout = self._get_next_retry_interval() - await timeout_deferred( - deferred=self.deferred, -- timeout=timeout, -+ timeout=self._timeout_interval, - clock=self.clock, - ) -- except Exception: -- pass -+ except defer.TimeoutError: -+ # Only increment the timeout interval if this was an actual timeout -+ self._timeout_interval = self._increment_timeout_interval() -+ except Exception as e: -+ logger.warning( -+ "Caught an exception while waiting on WaitingLock: %r", e -+ ) - - return await self._inner_lock.__aenter__() - -@@ -273,13 +277,13 @@ class WaitingLock: - - return r - -- def _get_next_retry_interval(self) -> float: -- next = self._retry_interval -- self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) -- if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations -+ def _increment_timeout_interval(self) -> float: -+ next = self._timeout_interval -+ next = min(Duration(minutes=15).as_secs(), next * 2) -+ if next > Duration(minutes=10).as_secs(): # >12 iterations - logger.warning( - "Lock timeout is getting excessive: %ss. There may be a deadlock.", -- self._retry_interval, -+ next, - ) - return next * random.uniform(0.9, 1.1) - -@@ -297,7 +301,7 @@ class WaitingMultiLock: - deferred: "defer.Deferred[None]" = attr.Factory(defer.Deferred) - - _inner_lock_cm: AsyncContextManager | None = None -- _retry_interval: float = 0.1 -+ _timeout_interval: float = 0.1 - _lock_span: "opentracing.Scope" = attr.Factory( - lambda: start_active_span("WaitingLock.lock") - ) -@@ -324,19 +328,23 @@ class WaitingMultiLock: - break - - try: -- # Wait until the we get notified the lock might have been -+ # Wait until the notification the lock might have been - # released (by the deferred being resolved). We also -- # periodically wake up in case the lock was released but we -+ # periodically wake up in case the lock was released, but we - # weren't notified. - with PreserveLoggingContext(): -- timeout = self._get_next_retry_interval() - await timeout_deferred( - deferred=self.deferred, -- timeout=timeout, -+ timeout=self._timeout_interval, - clock=self.clock, - ) -- except Exception: -- pass -+ except defer.TimeoutError: -+ # Only increment the timeout interval if this was an actual timeout -+ self._timeout_interval = self._increment_timeout_interval() -+ except Exception as e: -+ logger.warning( -+ "Caught an exception while waiting on WaitingMultiLock: %r", e -+ ) - - assert self._inner_lock_cm - await self._inner_lock_cm.__aenter__() -@@ -360,12 +368,12 @@ class WaitingMultiLock: - - return r - -- def _get_next_retry_interval(self) -> float: -- next = self._retry_interval -- self._retry_interval = min(Duration(minutes=15).as_secs(), next * 2) -- if self._retry_interval > Duration(minutes=10).as_secs(): # >12 iterations -+ def _increment_timeout_interval(self) -> float: -+ next = self._timeout_interval -+ next = min(Duration(minutes=15).as_secs(), next * 2) -+ if next > Duration(minutes=10).as_secs(): # >12 iterations - logger.warning( - "Lock timeout is getting excessive: %ss. There may be a deadlock.", -- self._retry_interval, -+ next, - ) - return next * random.uniform(0.9, 1.1) -diff --git a/tests/handlers/test_worker_lock.py b/tests/handlers/test_worker_lock.py -index 61ff51ff92..3ae44d48e2 100644 ---- a/tests/handlers/test_worker_lock.py -+++ b/tests/handlers/test_worker_lock.py -@@ -21,6 +21,7 @@ - - import logging - import platform -+from unittest.mock import patch - - from twisted.internet import defer - from twisted.internet.testing import MemoryReactor -@@ -48,13 +49,47 @@ class WorkerLockTestCase(unittest.HomeserverTestCase): - self.get_success(lock1.__aenter__()) - - lock2 = self.worker_lock_handler.acquire_lock("name", "key") -- d2 = defer.ensureDeferred(lock2.__aenter__()) -- self.assertNoResult(d2) -- -- self.get_success(lock1.__aexit__(None, None, None)) -+ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit -+ with patch.object( -+ lock2, -+ "_increment_timeout_interval", -+ wraps=lock2._increment_timeout_interval, -+ ) as wrapped_lock2_increment_timeout_interval_method: -+ d2 = defer.ensureDeferred(lock2.__aenter__()) -+ self.assertNoResult(d2) -+ -+ # The lock should not time out here -+ wrapped_lock2_increment_timeout_interval_method.assert_not_called() -+ self.get_success(lock1.__aexit__(None, None, None)) -+ -+ self.get_success(d2) -+ self.get_success(lock2.__aexit__(None, None, None)) -+ -+ def test_timeouts_for_lock_locally(self) -> None: -+ """Test timeouts are incremented for a lock on a single worker""" -+ lock1 = self.worker_lock_handler.acquire_lock("name", "key") -+ self.get_success(lock1.__aenter__()) - -- self.get_success(d2) -- self.get_success(lock2.__aexit__(None, None, None)) -+ lock2 = self.worker_lock_handler.acquire_lock("name", "key") -+ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit -+ with patch.object( -+ lock2, -+ "_increment_timeout_interval", -+ wraps=lock2._increment_timeout_interval, -+ ) as wrapped_lock2_increment_timeout_interval_method: -+ d2 = defer.ensureDeferred(lock2.__aenter__()) -+ self.assertNoResult(d2) -+ -+ # Recall that pump() will advance time of the given amount 100 times, this -+ # amounts to about 10 seconds passing -+ self.pump(10.0) -+ -+ # Should be timed out 6 times, but do not fail on that exact count -+ wrapped_lock2_increment_timeout_interval_method.assert_called() -+ self.get_success(lock1.__aexit__(None, None, None)) -+ -+ self.get_success(d2) -+ self.get_success(lock2.__aexit__(None, None, None)) - - def test_lock_contention(self) -> None: - """Test lock contention when a lot of locks wait on a single worker""" -@@ -117,10 +152,52 @@ class WorkerLockWorkersTestCase(BaseMultiWorkerStreamTestCase): - self.get_success(lock1.__aenter__()) - - lock2 = worker_lock_handler.acquire_lock("name", "key") -- d2 = defer.ensureDeferred(lock2.__aenter__()) -- self.assertNoResult(d2) -+ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit -+ with patch.object( -+ lock2, -+ "_increment_timeout_interval", -+ wraps=lock2._increment_timeout_interval, -+ ) as wrapped_lock2_increment_timeout_interval_method: -+ d2 = defer.ensureDeferred(lock2.__aenter__()) -+ self.assertNoResult(d2) -+ -+ # The lock should not time out here -+ wrapped_lock2_increment_timeout_interval_method.assert_not_called() -+ self.get_success(lock1.__aexit__(None, None, None)) -+ -+ self.get_success(d2) -+ self.get_success(lock2.__aexit__(None, None, None)) -+ -+ def test_timeouts_for_lock_worker(self) -> None: -+ """Test timeouts are incremented for a lock on another worker""" -+ worker = self.make_worker_hs( -+ "synapse.app.generic_worker", -+ extra_config={ -+ "redis": {"enabled": True}, -+ }, -+ ) -+ worker_lock_handler = worker.get_worker_locks_handler() - -- self.get_success(lock1.__aexit__(None, None, None)) -+ lock1 = self.main_worker_lock_handler.acquire_lock("name", "key") -+ self.get_success(lock1.__aenter__()) - -- self.get_success(d2) -- self.get_success(lock2.__aexit__(None, None, None)) -+ lock2 = worker_lock_handler.acquire_lock("name", "key") -+ # Wrap the WaitingLock object, so we can detect if the timeouts are being hit -+ with patch.object( -+ lock2, -+ "_increment_timeout_interval", -+ wraps=lock2._increment_timeout_interval, -+ ) as wrapped_lock2_increment_timeout_interval_method: -+ d2 = defer.ensureDeferred(lock2.__aenter__()) -+ self.assertNoResult(d2) -+ -+ # Recall that pump() will advance time of the given amount 100 times, this -+ # amounts to about 10 seconds passing -+ self.pump(0.1) -+ -+ # Should be timed out 6 times, but do not fail on that exact count -+ wrapped_lock2_increment_timeout_interval_method.assert_called() -+ self.get_success(lock1.__aexit__(None, None, None)) -+ -+ self.get_success(d2) -+ self.get_success(lock2.__aexit__(None, None, None)) --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0023-unecessarily-long-pump-in-test-left-over-from-testin.patch b/packages/overlays/matrix-synapse/patches/0023-unecessarily-long-pump-in-test-left-over-from-testin.patch new file mode 100644 index 0000000..5b11819 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0023-unecessarily-long-pump-in-test-left-over-from-testin.patch @@ -0,0 +1,26 @@ +From 60191c1dce61b42fc9eaeffad77ba573de072d0f Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Mon, 26 Jan 2026 11:18:29 -0600 +Subject: [PATCH 23/24] unecessarily long pump() in test, left over from + testing logging + +--- + tests/handlers/test_worker_lock.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/handlers/test_worker_lock.py b/tests/handlers/test_worker_lock.py +index 3ae44d48e2..c5c3ce22ef 100644 +--- a/tests/handlers/test_worker_lock.py ++++ b/tests/handlers/test_worker_lock.py +@@ -82,7 +82,7 @@ class WorkerLockTestCase(unittest.HomeserverTestCase): + + # Recall that pump() will advance time of the given amount 100 times, this + # amounts to about 10 seconds passing +- self.pump(10.0) ++ self.pump(0.1) + + # Should be timed out 6 times, but do not fail on that exact count + wrapped_lock2_increment_timeout_interval_method.assert_called() +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0024-adjust-changelog-again.patch b/packages/overlays/matrix-synapse/patches/0024-adjust-changelog-again.patch new file mode 100644 index 0000000..2926890 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0024-adjust-changelog-again.patch @@ -0,0 +1,19 @@ +From 887fd0bf50e451d63c1675de40712bf32c8baa7c Mon Sep 17 00:00:00 2001 +From: Jason Little +Date: Mon, 26 Jan 2026 11:18:38 -0600 +Subject: [PATCH 24/24] adjust changelog(again) + +--- + changelog.d/19394.bugfix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix +index 3591c0a745..02131d89ba 100644 +--- a/changelog.d/19394.bugfix ++++ b/changelog.d/19394.bugfix +@@ -1 +1 @@ +-Capped the `WorkerLock` retry interval to a maximum of 15 minutes to prevent dealing with excessively long numbers. Contributed by Famedly. ++Capped the `WorkerLock` time out interval to a maximum of 15 minutes to prevent dealing with excessively long numbers and prevent logging when the retry is not an actual time out. Contributed by Famedly. +-- +2.53.0 + diff --git a/packages/overlays/matrix-synapse/patches/0024-unecessarily-long-pump-in-test-left-over-from-testin.patch b/packages/overlays/matrix-synapse/patches/0024-unecessarily-long-pump-in-test-left-over-from-testin.patch deleted file mode 100644 index 119c299..0000000 --- a/packages/overlays/matrix-synapse/patches/0024-unecessarily-long-pump-in-test-left-over-from-testin.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 1a891a977868a708d2c805ef2e8ff2dfa2438106 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 11:18:29 -0600 -Subject: [PATCH 24/25] unecessarily long pump() in test, left over from - testing logging - ---- - tests/handlers/test_worker_lock.py | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tests/handlers/test_worker_lock.py b/tests/handlers/test_worker_lock.py -index 3ae44d48e2..c5c3ce22ef 100644 ---- a/tests/handlers/test_worker_lock.py -+++ b/tests/handlers/test_worker_lock.py -@@ -82,7 +82,7 @@ class WorkerLockTestCase(unittest.HomeserverTestCase): - - # Recall that pump() will advance time of the given amount 100 times, this - # amounts to about 10 seconds passing -- self.pump(10.0) -+ self.pump(0.1) - - # Should be timed out 6 times, but do not fail on that exact count - wrapped_lock2_increment_timeout_interval_method.assert_called() --- -2.53.0 - diff --git a/packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch b/packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch deleted file mode 100644 index b3667b0..0000000 --- a/packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 41d6399023ab3ae231de04addb9369f4a295b63d Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 11:18:38 -0600 -Subject: [PATCH 25/25] adjust changelog(again) - ---- - changelog.d/19394.bugfix | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/changelog.d/19394.bugfix b/changelog.d/19394.bugfix -index 3591c0a745..02131d89ba 100644 ---- a/changelog.d/19394.bugfix -+++ b/changelog.d/19394.bugfix -@@ -1 +1 @@ --Capped the `WorkerLock` retry interval to a maximum of 15 minutes to prevent dealing with excessively long numbers. Contributed by Famedly. -+Capped the `WorkerLock` time out interval to a maximum of 15 minutes to prevent dealing with excessively long numbers and prevent logging when the retry is not an actual time out. Contributed by Famedly. --- -2.53.0 - -- cgit 1.5.1