From fdbada601862d7d91ac2a523ee7b6a8bf03447da Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 21 Feb 2026 22:30:17 +0100 Subject: prosody? --- host/Rory-ovh/services/nginx/rory.gay/root.nix | 26 ++- host/Rory-ovh/services/prosody.nix | 13 ++ ...-were-probably-switched.-Set-max-to-arbit.patch | 39 ---- .../patches/0001-nix-use-postgres-17.patch | 25 ++ .../matrix-synapse/patches/0002-changelog.patch | 20 -- .../patches/0002-nix-fix-flake.patch | 190 +++++++++++++++ .../0003-Update-changelog.d-19394.bugfix.patch | 20 -- .../patches/0003-nix-Update-flake.patch | 61 +++++ ...the-retry-interval-actually-being-a-timeo.patch | 257 --------------------- .../0004-nix-Temporarily-disable-go-in-flake.patch | 26 +++ .../patches/0005-Add-test-script.patch | 22 ++ ...y-long-pump-in-test-left-over-from-testin.patch | 26 --- .../0006-Fix-gitignore-to-ignore-.venv.patch | 25 ++ .../patches/0006-adjust-changelog-again.patch | 19 -- .../patches/0007-Fast-auth-links.patch | 101 ++++++++ .../patches/0007-nix-use-postgres-17.patch | 25 -- ...h-logging-to-room-summary-over-federation.patch | 77 ++++++ .../patches/0008-nix-fix-flake.patch | 190 --------------- ...-entire-room-if-accessibility-check-fails.patch | 28 +++ .../patches/0009-nix-Update-flake.patch | 61 ----- .../0010-Log-policy-server-rejected-events.patch | 31 +++ .../0010-nix-Temporarily-disable-go-in-flake.patch | 26 --- .../patches/0011-Add-test-script.patch | 22 -- ...-Use-parse_boolean-for-unredacted-content.patch | 30 +++ .../0012-Expose-tombstone-in-room-admin-api.patch | 115 +++++++++ .../0012-Fix-gitignore-to-ignore-.venv.patch | 25 -- .../patches/0013-Fast-auth-links.patch | 101 -------- ...recheck-messages-pagination-data-if-a-bac.patch | 196 ++++++++++++++++ ...h-logging-to-room-summary-over-federation.patch | 77 ------ ...nation-with-large-gaps-of-rejected-events.patch | 51 ++++ ...-entire-room-if-accessibility-check-fails.patch | 28 --- ...5-RequestRatelimiter-expose-can_do_action.patch | 96 ++++++++ ..._event_ids-assert-in-event-creation-handl.patch | 26 +++ .../0016-Log-policy-server-rejected-events.patch | 31 --- .../0017-Add-bulk-send-events-endpoint.patch | 196 ++++++++++++++++ ...-Use-parse_boolean-for-unredacted-content.patch | 30 --- .../0018-Expose-tombstone-in-room-admin-api.patch | 115 --------- .../patches/0018-admin-api-send-more-data.patch | 170 ++++++++++++++ ...llow-overriding-max-background-task-count.patch | 52 +++++ ...recheck-messages-pagination-data-if-a-bac.patch | 196 ---------------- ...nation-with-large-gaps-of-rejected-events.patch | 51 ---- ...-were-probably-switched.-Set-max-to-arbit.patch | 39 ++++ ...1-RequestRatelimiter-expose-can_do_action.patch | 96 -------- .../matrix-synapse/patches/0021-changelog.patch | 20 ++ ..._event_ids-assert-in-event-creation-handl.patch | 26 --- .../0022-Update-changelog.d-19394.bugfix.patch | 20 ++ .../0023-Add-bulk-send-events-endpoint.patch | 196 ---------------- ...the-retry-interval-actually-being-a-timeo.patch | 257 +++++++++++++++++++++ .../patches/0024-admin-api-send-more-data.patch | 170 -------------- ...y-long-pump-in-test-left-over-from-testin.patch | 26 +++ ...llow-overriding-max-background-task-count.patch | 52 ----- .../patches/0025-adjust-changelog-again.patch | 19 ++ 52 files changed, 1937 insertions(+), 1900 deletions(-) create mode 100644 host/Rory-ovh/services/prosody.nix delete mode 100644 packages/overlays/matrix-synapse/patches/0001-max-and-min-were-probably-switched.-Set-max-to-arbit.patch create mode 100644 packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0002-changelog.patch create mode 100644 packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0003-Update-changelog.d-19394.bugfix.patch create mode 100644 packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0004-Adjust-for-the-retry-interval-actually-being-a-timeo.patch create mode 100644 packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch create mode 100644 packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0005-unecessarily-long-pump-in-test-left-over-from-testin.patch create mode 100644 packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0006-adjust-changelog-again.patch create mode 100644 packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0007-nix-use-postgres-17.patch create mode 100644 packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0008-nix-fix-flake.patch create mode 100644 packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0009-nix-Update-flake.patch create mode 100644 packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0010-nix-Temporarily-disable-go-in-flake.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0011-Add-test-script.patch create mode 100644 packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch create mode 100644 packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0012-Fix-gitignore-to-ignore-.venv.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0013-Fast-auth-links.patch create 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-Add-too-much-logging-to-room-summary-over-federation.patch create mode 100644 packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0015-Log-entire-room-if-accessibility-check-fails.patch create mode 100644 packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch create 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/0016-Log-policy-server-rejected-events.patch create mode 100644 packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0017-Use-parse_boolean-for-unredacted-content.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0018-Expose-tombstone-in-room-admin-api.patch create mode 100644 packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch create mode 100644 packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0019-fix-Always-recheck-messages-pagination-data-if-a-bac.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0020-Fix-pagination-with-large-gaps-of-rejected-events.patch create mode 100644 packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0021-RequestRatelimiter-expose-can_do_action.patch create mode 100644 packages/overlays/matrix-synapse/patches/0021-changelog.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0022-Clarify-pre_event_ids-assert-in-event-creation-handl.patch create mode 100644 packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0023-Add-bulk-send-events-endpoint.patch create mode 100644 packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch delete mode 100644 packages/overlays/matrix-synapse/patches/0024-admin-api-send-more-data.patch create 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-Allow-overriding-max-background-task-count.patch create mode 100644 packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch diff --git a/host/Rory-ovh/services/nginx/rory.gay/root.nix b/host/Rory-ovh/services/nginx/rory.gay/root.nix index 2f491cf..895ae77 100755 --- a/host/Rory-ovh/services/nginx/rory.gay/root.nix +++ b/host/Rory-ovh/services/nginx/rory.gay/root.nix @@ -3,7 +3,7 @@ enableACME = !config.virtualisation.isVmVariant; addSSL = !config.virtualisation.isVmVariant; root = "/data/nginx/html_rory_gay"; - extraConfig = ''autoindex on;''; + extraConfig = "autoindex on;"; locations."= /.well-known/matrix/server".extraConfig = '' more_set_headers 'Content-Type application/json'; @@ -46,4 +46,28 @@ } }'; ''; + + locations."= /.well-known/host-meta".extraConfig = '' + more_set_headers 'Content-Type application/json'; + more_set_headers 'Access-Control-Allow-Origin *'; + return 200 ' + + + '; + ''; + locations."= /.well-known/host-meta.json".extraConfig = '' + more_set_headers 'Content-Type application/json'; + more_set_headers 'Access-Control-Allow-Origin *'; + return 200 '${ + builtins.toJSON { + links = [ + { + rel = "urn:xmpp:alt-connections:websocket"; + href = "wss://xmpp.rory.gay/ws"; + } + ]; + } + }'; + ''; } diff --git a/host/Rory-ovh/services/prosody.nix b/host/Rory-ovh/services/prosody.nix new file mode 100644 index 0000000..53743a4 --- /dev/null +++ b/host/Rory-ovh/services/prosody.nix @@ -0,0 +1,13 @@ +{ lib, pkgs, ... }: +{ + services.prosody = { + enable = true; + virtualhosts."rory.gay" = { + enabled = true; + domain = "rory.gay"; + }; + admins = [ + "emma@rory.gay" + ]; + }; +} diff --git a/packages/overlays/matrix-synapse/patches/0001-max-and-min-were-probably-switched.-Set-max-to-arbit.patch b/packages/overlays/matrix-synapse/patches/0001-max-and-min-were-probably-switched.-Set-max-to-arbit.patch deleted file mode 100644 index 60df8a5..0000000 --- a/packages/overlays/matrix-synapse/patches/0001-max-and-min-were-probably-switched.-Set-max-to-arbit.patch +++ /dev/null @@ -1,39 +0,0 @@ -From ff23d0011f30988a043d2f9817d96b9ba483c266 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Fri, 2 Jan 2026 12:48:22 -0600 -Subject: [PATCH 01/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.52.0 - 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 new file mode 100644 index 0000000..7e079b6 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0001-nix-use-postgres-17.patch @@ -0,0 +1,25 @@ +From 0710094b06b2b3eaad8212d24edd10066648cb17 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 + +Signed-off-by: Rory& +--- + flake.nix | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/flake.nix b/flake.nix +index 4ff6518aed..51ae12c272 100644 +--- a/flake.nix ++++ b/flake.nix +@@ -152,6 +152,7 @@ + # Postgres is needed to run Synapse with postgres support and + # to run certain unit tests that require postgres. + services.postgres.enable = true; ++ services.postgres.package = pkgs.postgresql_17; + + # On the first invocation of `devenv up`, create a database for + # Synapse to store data in. +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0002-changelog.patch b/packages/overlays/matrix-synapse/patches/0002-changelog.patch deleted file mode 100644 index 33e6e8d..0000000 --- a/packages/overlays/matrix-synapse/patches/0002-changelog.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 548c85b0fb604718ff52f987defba77ed3d21c10 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Tue, 20 Jan 2026 06:42:18 -0600 -Subject: [PATCH 02/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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch b/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch new file mode 100644 index 0000000..014d2f5 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0002-nix-fix-flake.patch @@ -0,0 +1,190 @@ +From 3191ff6411778d266986ada3a68fd90427a9d283 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 + +Signed-off-by: Rory& +--- + flake.lock | 58 +++++++++++++++++++----------------------------------- + flake.nix | 10 +++++++++- + 2 files changed, 29 insertions(+), 39 deletions(-) + +diff --git a/flake.lock b/flake.lock +index a6a2aea328..4e2f01153b 100644 +--- a/flake.lock ++++ b/flake.lock +@@ -39,15 +39,12 @@ + } + }, + "flake-utils": { +- "inputs": { +- "systems": "systems" +- }, + "locked": { +- "lastModified": 1685518550, +- "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", ++ "lastModified": 1667395993, ++ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "owner": "numtide", + "repo": "flake-utils", +- "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", ++ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "type": "github" + }, + "original": { +@@ -152,27 +149,27 @@ + }, + "nixpkgs-stable": { + "locked": { +- "lastModified": 1685801374, +- "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", ++ "lastModified": 1678872516, ++ "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", + "owner": "NixOS", + "repo": "nixpkgs", +- "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", ++ "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8", + "type": "github" + }, + "original": { + "owner": "NixOS", +- "ref": "nixos-23.05", ++ "ref": "nixos-22.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { +- "lastModified": 1729265718, +- "narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=", ++ "lastModified": 1748217807, ++ "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=", + "owner": "NixOS", + "repo": "nixpkgs", +- "rev": "ccc0c2126893dd20963580b6478d1a10a4512185", ++ "rev": "3108eaa516ae22c2360928589731a4f1581526ef", + "type": "github" + }, + "original": { +@@ -184,11 +181,11 @@ + }, + "nixpkgs_3": { + "locked": { +- "lastModified": 1728538411, +- "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", ++ "lastModified": 1744536153, ++ "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", + "owner": "NixOS", + "repo": "nixpkgs", +- "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", ++ "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", + "type": "github" + }, + "original": { +@@ -213,11 +210,11 @@ + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { +- "lastModified": 1688056373, +- "narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=", ++ "lastModified": 1686050334, ++ "narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", +- "rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7", ++ "rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc", + "type": "github" + }, + "original": { +@@ -231,7 +228,7 @@ + "devenv": "devenv", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay", +- "systems": "systems_2" ++ "systems": "systems" + } + }, + "rust-overlay": { +@@ -239,11 +236,11 @@ + "nixpkgs": "nixpkgs_3" + }, + "locked": { +- "lastModified": 1731897198, +- "narHash": "sha256-Ou7vLETSKwmE/HRQz4cImXXJBr/k9gp4J4z/PF8LzTE=", ++ "lastModified": 1748313401, ++ "narHash": "sha256-x5UuDKP2Ui/TresAngUo9U4Ss9xfOmN8dAXU8OrkZmA=", + "owner": "oxalica", + "repo": "rust-overlay", +- "rev": "0be641045af6d8666c11c2c40e45ffc9667839b5", ++ "rev": "9c8ea175cf9af29edbcff121512e44092a8f37e4", + "type": "github" + }, + "original": { +@@ -266,21 +263,6 @@ + "repo": "default", + "type": "github" + } +- }, +- "systems_2": { +- "locked": { +- "lastModified": 1681028828, +- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", +- "owner": "nix-systems", +- "repo": "default", +- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", +- "type": "github" +- }, +- "original": { +- "owner": "nix-systems", +- "repo": "default", +- "type": "github" +- } + } + }, + "root": "root", +diff --git a/flake.nix b/flake.nix +index 51ae12c272..cc41490a41 100644 +--- a/flake.nix ++++ b/flake.nix +@@ -82,7 +82,7 @@ + # + # NOTE: We currently need to set the Rust version unnecessarily high + # in order to work around https://github.com/matrix-org/synapse/issues/15939 +- (rust-bin.stable."1.82.0".default.override { ++ (rust-bin.stable."1.87.0".default.override { + # Additionally install the "rust-src" extension to allow diving into the + # Rust source code in an IDE (rust-analyzer will also make use of it). + extensions = [ "rust-src" ]; +@@ -117,6 +117,8 @@ + # For releasing Synapse + debian-devscripts # (`dch` for manipulating the Debian changelog) + libnotify # (the release script uses `notify-send` to tell you when CI jobs are done) ++ ++ postgresql.pg_config + ]; + + # Install Python and manage a virtualenv with Poetry. +@@ -139,6 +141,9 @@ + # force compiling those binaries locally instead. + env.POETRY_INSTALLER_NO_BINARY = "ruff"; + ++ # Required to make git work ++ env.CARGO_NET_GIT_FETCH_WITH_CLI = "true"; ++ + # Install dependencies for the additional programming languages + # involved with Synapse development. + # +@@ -160,6 +165,9 @@ + services.postgres.initialDatabases = [ + { name = "synapse"; } + ]; ++ ++ services.postgres.port = 5433; ++ + # Create a postgres user called 'synapse_user' which has ownership + # over the 'synapse' database. + services.postgres.initialScript = '' +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0003-Update-changelog.d-19394.bugfix.patch b/packages/overlays/matrix-synapse/patches/0003-Update-changelog.d-19394.bugfix.patch deleted file mode 100644 index e66e80c..0000000 --- a/packages/overlays/matrix-synapse/patches/0003-Update-changelog.d-19394.bugfix.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 1d22f90bd3368caeb620d82f233fa1708eda4811 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Wed, 21 Jan 2026 06:57:34 -0600 -Subject: [PATCH 03/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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch b/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch new file mode 100644 index 0000000..cd8148f --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0003-nix-Update-flake.patch @@ -0,0 +1,61 @@ +From 7bc7375545daed0571933e2a6063ecb0f78fecf7 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 + +Signed-off-by: Rory& +--- + flake.lock | 12 ++++++------ + flake.nix | 2 +- + 2 files changed, 7 insertions(+), 7 deletions(-) + +diff --git a/flake.lock b/flake.lock +index 4e2f01153b..0f2de20b2b 100644 +--- a/flake.lock ++++ b/flake.lock +@@ -165,11 +165,11 @@ + }, + "nixpkgs_2": { + "locked": { +- "lastModified": 1748217807, +- "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=", ++ "lastModified": 1762943920, ++ "narHash": "sha256-ITeH8GBpQTw9457ICZBddQEBjlXMmilML067q0e6vqY=", + "owner": "NixOS", + "repo": "nixpkgs", +- "rev": "3108eaa516ae22c2360928589731a4f1581526ef", ++ "rev": "91c9a64ce2a84e648d0cf9671274bb9c2fb9ba60", + "type": "github" + }, + "original": { +@@ -236,11 +236,11 @@ + "nixpkgs": "nixpkgs_3" + }, + "locked": { +- "lastModified": 1748313401, +- "narHash": "sha256-x5UuDKP2Ui/TresAngUo9U4Ss9xfOmN8dAXU8OrkZmA=", ++ "lastModified": 1763001554, ++ "narHash": "sha256-wsfhRTuxu6f06RMmP4JWcq3wWRlmYtQaJZ6b3f+EJ94=", + "owner": "oxalica", + "repo": "rust-overlay", +- "rev": "9c8ea175cf9af29edbcff121512e44092a8f37e4", ++ "rev": "315d97eb753cee8e1aa039a5e622b84d32a454bb", + "type": "github" + }, + "original": { +diff --git a/flake.nix b/flake.nix +index cc41490a41..5bde1e6c07 100644 +--- a/flake.nix ++++ b/flake.nix +@@ -82,7 +82,7 @@ + # + # NOTE: We currently need to set the Rust version unnecessarily high + # in order to work around https://github.com/matrix-org/synapse/issues/15939 +- (rust-bin.stable."1.87.0".default.override { ++ (rust-bin.stable."1.88.0".default.override { + # Additionally install the "rust-src" extension to allow diving into the + # Rust source code in an IDE (rust-analyzer will also make use of it). + extensions = [ "rust-src" ]; +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0004-Adjust-for-the-retry-interval-actually-being-a-timeo.patch b/packages/overlays/matrix-synapse/patches/0004-Adjust-for-the-retry-interval-actually-being-a-timeo.patch deleted file mode 100644 index eb11a01..0000000 --- a/packages/overlays/matrix-synapse/patches/0004-Adjust-for-the-retry-interval-actually-being-a-timeo.patch +++ /dev/null @@ -1,257 +0,0 @@ -From e864cfe25f590dd75b31ad6e8ca0dccccf48df60 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 10:26:38 -0600 -Subject: [PATCH 04/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.52.0 - 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 new file mode 100644 index 0000000..ed4e7f5 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0004-nix-Temporarily-disable-go-in-flake.patch @@ -0,0 +1,26 @@ +From ed37b0cde78794056afcae9030e38d97f010336d 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 + +Signed-off-by: Rory& +--- + flake.nix | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/flake.nix b/flake.nix +index 5bde1e6c07..cf7a731f51 100644 +--- a/flake.nix ++++ b/flake.nix +@@ -151,7 +151,7 @@ + # * Perl is needed to run the SyTest test suite. + # * Rust is used for developing and running Synapse. + # It is installed manually with `packages` above. +- languages.go.enable = true; ++ #languages.go.enable = true; + languages.perl.enable = true; + + # Postgres is needed to run Synapse with postgres support and +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch b/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch new file mode 100644 index 0000000..60157ca --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0005-Add-test-script.patch @@ -0,0 +1,22 @@ +From d5295e735828d424d9217d7127ece66cad177986 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 + +Signed-off-by: Rory& +--- + scripts-dev/run-tests.sh | 2 ++ + 1 file changed, 2 insertions(+) + create mode 100755 scripts-dev/run-tests.sh + +diff --git a/scripts-dev/run-tests.sh b/scripts-dev/run-tests.sh +new file mode 100755 +index 0000000000..1ac82801b2 +--- /dev/null ++++ b/scripts-dev/run-tests.sh +@@ -0,0 +1,2 @@ ++#! /usr/bin/env sh ++poetry run trial -j`nproc` tests +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0005-unecessarily-long-pump-in-test-left-over-from-testin.patch b/packages/overlays/matrix-synapse/patches/0005-unecessarily-long-pump-in-test-left-over-from-testin.patch deleted file mode 100644 index f39a6b5..0000000 --- a/packages/overlays/matrix-synapse/patches/0005-unecessarily-long-pump-in-test-left-over-from-testin.patch +++ /dev/null @@ -1,26 +0,0 @@ -From d416dc8c41ab43df323ae4ba16341623c55fff6f Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 11:18:29 -0600 -Subject: [PATCH 05/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.52.0 - 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 new file mode 100644 index 0000000..1722347 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0006-Fix-gitignore-to-ignore-.venv.patch @@ -0,0 +1,25 @@ +From 93af91e5d4eb59c8f1a55d5d3f86746d9728d28a 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 + +Signed-off-by: Rory& +--- + .gitignore | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/.gitignore b/.gitignore +index e333f2320b..3aec96e75e 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -30,6 +30,7 @@ __pycache__/ + /*.signing.key + /env/ + /.venv*/ ++/.venv + /homeserver*.yaml + /logs + /media_store/ +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0006-adjust-changelog-again.patch b/packages/overlays/matrix-synapse/patches/0006-adjust-changelog-again.patch deleted file mode 100644 index c41f5f5..0000000 --- a/packages/overlays/matrix-synapse/patches/0006-adjust-changelog-again.patch +++ /dev/null @@ -1,19 +0,0 @@ -From e555cd68f25d6f81f4a6cdaf28ecda74a043ae3e Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Mon, 26 Jan 2026 11:18:38 -0600 -Subject: [PATCH 06/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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch b/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch new file mode 100644 index 0000000..1891da7 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0007-Fast-auth-links.patch @@ -0,0 +1,101 @@ +From 7bf6dd9744da4eaccf201e62eef243b5e6c95314 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 + +Signed-off-by: Rory& +--- + synapse/storage/database.py | 43 +++++++++++++++++++ + .../databases/main/event_federation.py | 8 ++-- + 2 files changed, 47 insertions(+), 4 deletions(-) + +diff --git a/synapse/storage/database.py b/synapse/storage/database.py +index 6e38b55686..2bab1e53c5 100644 +--- a/synapse/storage/database.py ++++ b/synapse/storage/database.py +@@ -2608,6 +2608,49 @@ class DatabasePool: + + return txn.fetchall() + ++# requires database_engine.supports_using_any_list to be true ++def make_select_id_if_found_sql_clause( ++ database_engine: BaseDatabaseEngine, ++ column: str, ++ table: str, ++ iterable: Collection[Any], ++ *, ++ negative: bool = False, ++) -> tuple[str, list]: ++ """Returns an SQL clause that checks the given column is in the iterable. ++ ++ On SQLite this expands to `column IN (?, ?, ...)`, whereas on Postgres ++ it expands to `column = ANY(?)`. While both DBs support the `IN` form, ++ using the `ANY` form on postgres means that it views queries with ++ different length iterables as the same, helping the query stats. ++ ++ Args: ++ database_engine ++ column: Name of the column ++ table: Name of the table ++ iterable: The values to check the column against. ++ negative: Whether we should check for inequality, i.e. `NOT IN` ++ ++ Returns: ++ A tuple of SQL query and the args ++ """ ++ # This should hopefully be faster, but also makes postgres query ++ # stats easier to understand. ++ if database_engine.supports_using_any_list: ++ if not negative: ++ clause = f"{column}_lookup AS {column} FROM UNNEST(?::bigint[]) {column}_lookup WHERE EXISTS(SELECT FROM {table} WHERE {column}={column}_lookup)" ++ else: ++ clause = f"{column}_lookup AS {column} FROM UNNEST(?::bigint[]) {column}_lookup WHERE NOT EXISTS(SELECT FROM {table} WHERE {column}={column}_lookup)" ++ ++ return clause, [list(iterable)] ++ else: ++ params = ",".join("?" for _ in iterable) ++ if not negative: ++ clause = f"DISTINCT {column} FROM {table} WHERE {column} IN ({params})" ++ else: ++ clause = f"DISTINCT {column} FROM {table} WHERE {column} NOT IN ({params})" ++ return clause, list(iterable) ++ + + def make_in_list_sql_clause( + database_engine: BaseDatabaseEngine, +diff --git a/synapse/storage/databases/main/event_federation.py b/synapse/storage/databases/main/event_federation.py +index cc7083b605..55a0714f14 100644 +--- a/synapse/storage/databases/main/event_federation.py ++++ b/synapse/storage/databases/main/event_federation.py +@@ -47,6 +47,7 @@ from synapse.storage.database import ( + DatabasePool, + LoggingDatabaseConnection, + LoggingTransaction, ++ make_select_id_if_found_sql_clause, + ) + from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore + from synapse.storage.databases.main.events_worker import EventsWorkerStore +@@ -384,8 +385,7 @@ class EventFederationWorkerStore( + sql = """ + WITH RECURSIVE links(chain_id) AS ( + SELECT +- DISTINCT origin_chain_id +- FROM event_auth_chain_links WHERE %s ++ %s + UNION + SELECT + target_chain_id +@@ -402,8 +402,8 @@ class EventFederationWorkerStore( + while chains_to_fetch: + batch2 = tuple(itertools.islice(chains_to_fetch, 1000)) + chains_to_fetch.difference_update(batch2) +- clause, args = make_in_list_sql_clause( +- txn.database_engine, "origin_chain_id", batch2 ++ clause, args = make_select_id_if_found_sql_clause( ++ txn.database_engine, "origin_chain_id", "event_auth_chain_links", batch2 + ) + txn.execute(sql % (clause,), args) + +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0007-nix-use-postgres-17.patch b/packages/overlays/matrix-synapse/patches/0007-nix-use-postgres-17.patch deleted file mode 100644 index 24afa44..0000000 --- a/packages/overlays/matrix-synapse/patches/0007-nix-use-postgres-17.patch +++ /dev/null @@ -1,25 +0,0 @@ -From a347a2e48ca378902198c55b6e505d291f5c0440 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Fri, 25 Jul 2025 08:25:28 +0200 -Subject: [PATCH 07/25] nix: use postgres 17 - -Signed-off-by: Rory& ---- - flake.nix | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/flake.nix b/flake.nix -index 4ff6518aed..51ae12c272 100644 ---- a/flake.nix -+++ b/flake.nix -@@ -152,6 +152,7 @@ - # Postgres is needed to run Synapse with postgres support and - # to run certain unit tests that require postgres. - services.postgres.enable = true; -+ services.postgres.package = pkgs.postgresql_17; - - # On the first invocation of `devenv up`, create a database for - # Synapse to store data in. --- -2.52.0 - 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 new file mode 100644 index 0000000..a4cf1fb --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0008-Add-too-much-logging-to-room-summary-over-federation.patch @@ -0,0 +1,77 @@ +From 4d1ba2cb8982f0f72116753da80744947a09f373 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 + +Signed-off-by: Rory& +--- + synapse/handlers/room_summary.py | 40 ++++++++++++++++++++++++++++---- + 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 +--- a/synapse/handlers/room_summary.py ++++ b/synapse/handlers/room_summary.py +@@ -748,23 +748,55 @@ class RoomSummaryHandler: + """ + # The API doesn't return the room version so assume that a + # join rule of knock is valid. ++ join_rule = room.get("join_rule") ++ world_readable = room.get("world_readable") ++ ++ logger.warning( ++ "[EMMA] Checking if room %s is accessible to %s: join_rule=%s, world_readable=%s", ++ room_id, requester, join_rule, world_readable ++ ) ++ + if ( +- room.get("join_rule", JoinRules.PUBLIC) +- in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED) +- or room.get("world_readable") is True ++ join_rule in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED) ++ or world_readable is True + ): + return True +- elif not requester: ++ else: ++ logger.warning( ++ "[EMMA] Room %s is not accessible to %s: join_rule=%s, world_readable=%s, join_rule result=%s, world_readable result=%s", ++ room_id, requester, join_rule, world_readable, ++ join_rule in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED), ++ world_readable is True ++ ) ++ ++ if not requester: ++ logger.warning( ++ "[EMMA] No requester, so room %s is not accessible", ++ room_id ++ ) + return False ++ + + # Check if the user is a member of any of the allowed rooms from the response. + allowed_rooms = room.get("allowed_room_ids") ++ logger.warning( ++ "[EMMA] Checking if room %s is in allowed rooms for %s: join_rule=%s, allowed_rooms=%s", ++ requester, ++ room_id, ++ join_rule, ++ allowed_rooms ++ ) + if allowed_rooms and isinstance(allowed_rooms, list): + if await self._event_auth_handler.is_user_in_rooms( + allowed_rooms, requester + ): + return True + ++ logger.warning( ++ "[EMMA] Checking if room %s is accessble to %s via local state", ++ room_id, ++ requester ++ ) + # Finally, check locally if we can access the room. The user might + # already be in the room (if it was a child room), or there might be a + # pending invite, etc. +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0008-nix-fix-flake.patch b/packages/overlays/matrix-synapse/patches/0008-nix-fix-flake.patch deleted file mode 100644 index adc77f9..0000000 --- a/packages/overlays/matrix-synapse/patches/0008-nix-fix-flake.patch +++ /dev/null @@ -1,190 +0,0 @@ -From 52a291a179bde8f829d4939eef8d1ac29cbbe214 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Mon, 9 Jun 2025 17:38:34 +0200 -Subject: [PATCH 08/25] nix: fix flake - -Signed-off-by: Rory& ---- - flake.lock | 58 +++++++++++++++++++----------------------------------- - flake.nix | 10 +++++++++- - 2 files changed, 29 insertions(+), 39 deletions(-) - -diff --git a/flake.lock b/flake.lock -index a6a2aea328..4e2f01153b 100644 ---- a/flake.lock -+++ b/flake.lock -@@ -39,15 +39,12 @@ - } - }, - "flake-utils": { -- "inputs": { -- "systems": "systems" -- }, - "locked": { -- "lastModified": 1685518550, -- "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", -+ "lastModified": 1667395993, -+ "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", - "owner": "numtide", - "repo": "flake-utils", -- "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", -+ "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", - "type": "github" - }, - "original": { -@@ -152,27 +149,27 @@ - }, - "nixpkgs-stable": { - "locked": { -- "lastModified": 1685801374, -- "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", -+ "lastModified": 1678872516, -+ "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", - "owner": "NixOS", - "repo": "nixpkgs", -- "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", -+ "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8", - "type": "github" - }, - "original": { - "owner": "NixOS", -- "ref": "nixos-23.05", -+ "ref": "nixos-22.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { -- "lastModified": 1729265718, -- "narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=", -+ "lastModified": 1748217807, -+ "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=", - "owner": "NixOS", - "repo": "nixpkgs", -- "rev": "ccc0c2126893dd20963580b6478d1a10a4512185", -+ "rev": "3108eaa516ae22c2360928589731a4f1581526ef", - "type": "github" - }, - "original": { -@@ -184,11 +181,11 @@ - }, - "nixpkgs_3": { - "locked": { -- "lastModified": 1728538411, -- "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", -+ "lastModified": 1744536153, -+ "narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=", - "owner": "NixOS", - "repo": "nixpkgs", -- "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", -+ "rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11", - "type": "github" - }, - "original": { -@@ -213,11 +210,11 @@ - "nixpkgs-stable": "nixpkgs-stable" - }, - "locked": { -- "lastModified": 1688056373, -- "narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=", -+ "lastModified": 1686050334, -+ "narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=", - "owner": "cachix", - "repo": "pre-commit-hooks.nix", -- "rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7", -+ "rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc", - "type": "github" - }, - "original": { -@@ -231,7 +228,7 @@ - "devenv": "devenv", - "nixpkgs": "nixpkgs_2", - "rust-overlay": "rust-overlay", -- "systems": "systems_2" -+ "systems": "systems" - } - }, - "rust-overlay": { -@@ -239,11 +236,11 @@ - "nixpkgs": "nixpkgs_3" - }, - "locked": { -- "lastModified": 1731897198, -- "narHash": "sha256-Ou7vLETSKwmE/HRQz4cImXXJBr/k9gp4J4z/PF8LzTE=", -+ "lastModified": 1748313401, -+ "narHash": "sha256-x5UuDKP2Ui/TresAngUo9U4Ss9xfOmN8dAXU8OrkZmA=", - "owner": "oxalica", - "repo": "rust-overlay", -- "rev": "0be641045af6d8666c11c2c40e45ffc9667839b5", -+ "rev": "9c8ea175cf9af29edbcff121512e44092a8f37e4", - "type": "github" - }, - "original": { -@@ -266,21 +263,6 @@ - "repo": "default", - "type": "github" - } -- }, -- "systems_2": { -- "locked": { -- "lastModified": 1681028828, -- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", -- "owner": "nix-systems", -- "repo": "default", -- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", -- "type": "github" -- }, -- "original": { -- "owner": "nix-systems", -- "repo": "default", -- "type": "github" -- } - } - }, - "root": "root", -diff --git a/flake.nix b/flake.nix -index 51ae12c272..cc41490a41 100644 ---- a/flake.nix -+++ b/flake.nix -@@ -82,7 +82,7 @@ - # - # NOTE: We currently need to set the Rust version unnecessarily high - # in order to work around https://github.com/matrix-org/synapse/issues/15939 -- (rust-bin.stable."1.82.0".default.override { -+ (rust-bin.stable."1.87.0".default.override { - # Additionally install the "rust-src" extension to allow diving into the - # Rust source code in an IDE (rust-analyzer will also make use of it). - extensions = [ "rust-src" ]; -@@ -117,6 +117,8 @@ - # For releasing Synapse - debian-devscripts # (`dch` for manipulating the Debian changelog) - libnotify # (the release script uses `notify-send` to tell you when CI jobs are done) -+ -+ postgresql.pg_config - ]; - - # Install Python and manage a virtualenv with Poetry. -@@ -139,6 +141,9 @@ - # force compiling those binaries locally instead. - env.POETRY_INSTALLER_NO_BINARY = "ruff"; - -+ # Required to make git work -+ env.CARGO_NET_GIT_FETCH_WITH_CLI = "true"; -+ - # Install dependencies for the additional programming languages - # involved with Synapse development. - # -@@ -160,6 +165,9 @@ - services.postgres.initialDatabases = [ - { name = "synapse"; } - ]; -+ -+ services.postgres.port = 5433; -+ - # Create a postgres user called 'synapse_user' which has ownership - # over the 'synapse' database. - services.postgres.initialScript = '' --- -2.52.0 - 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 new file mode 100644 index 0000000..d189ca2 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0009-Log-entire-room-if-accessibility-check-fails.patch @@ -0,0 +1,28 @@ +From 521c06d64c7a22bea6db9a78337c53b70c44cbe3 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 + +Signed-off-by: Rory& +--- + synapse/handlers/room_summary.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/synapse/handlers/room_summary.py b/synapse/handlers/room_summary.py +index 5ea32af620..30ee91cd95 100644 +--- a/synapse/handlers/room_summary.py ++++ b/synapse/handlers/room_summary.py +@@ -964,6 +964,10 @@ class RoomSummaryHandler: + if not room_entry or not await self._is_remote_room_accessible( + requester, room_entry.room_id, room_entry.room + ): ++ logger.warning( ++ "[Emma] Room entry contents: %s", ++ room_entry.room if room_entry else None ++ ) + raise NotFoundError("Room not found or is not accessible") + + room = dict(room_entry.room) +-- +2.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0009-nix-Update-flake.patch b/packages/overlays/matrix-synapse/patches/0009-nix-Update-flake.patch deleted file mode 100644 index 279c480..0000000 --- a/packages/overlays/matrix-synapse/patches/0009-nix-Update-flake.patch +++ /dev/null @@ -1,61 +0,0 @@ -From b75e1462eea39068dd189755ffb0d9fb466c59cc Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Thu, 13 Nov 2025 13:57:10 +0100 -Subject: [PATCH 09/25] nix: Update flake - -Signed-off-by: Rory& ---- - flake.lock | 12 ++++++------ - flake.nix | 2 +- - 2 files changed, 7 insertions(+), 7 deletions(-) - -diff --git a/flake.lock b/flake.lock -index 4e2f01153b..0f2de20b2b 100644 ---- a/flake.lock -+++ b/flake.lock -@@ -165,11 +165,11 @@ - }, - "nixpkgs_2": { - "locked": { -- "lastModified": 1748217807, -- "narHash": "sha256-P3u2PXxMlo49PutQLnk2PhI/imC69hFl1yY4aT5Nax8=", -+ "lastModified": 1762943920, -+ "narHash": "sha256-ITeH8GBpQTw9457ICZBddQEBjlXMmilML067q0e6vqY=", - "owner": "NixOS", - "repo": "nixpkgs", -- "rev": "3108eaa516ae22c2360928589731a4f1581526ef", -+ "rev": "91c9a64ce2a84e648d0cf9671274bb9c2fb9ba60", - "type": "github" - }, - "original": { -@@ -236,11 +236,11 @@ - "nixpkgs": "nixpkgs_3" - }, - "locked": { -- "lastModified": 1748313401, -- "narHash": "sha256-x5UuDKP2Ui/TresAngUo9U4Ss9xfOmN8dAXU8OrkZmA=", -+ "lastModified": 1763001554, -+ "narHash": "sha256-wsfhRTuxu6f06RMmP4JWcq3wWRlmYtQaJZ6b3f+EJ94=", - "owner": "oxalica", - "repo": "rust-overlay", -- "rev": "9c8ea175cf9af29edbcff121512e44092a8f37e4", -+ "rev": "315d97eb753cee8e1aa039a5e622b84d32a454bb", - "type": "github" - }, - "original": { -diff --git a/flake.nix b/flake.nix -index cc41490a41..5bde1e6c07 100644 ---- a/flake.nix -+++ b/flake.nix -@@ -82,7 +82,7 @@ - # - # NOTE: We currently need to set the Rust version unnecessarily high - # in order to work around https://github.com/matrix-org/synapse/issues/15939 -- (rust-bin.stable."1.87.0".default.override { -+ (rust-bin.stable."1.88.0".default.override { - # Additionally install the "rust-src" extension to allow diving into the - # Rust source code in an IDE (rust-analyzer will also make use of it). - extensions = [ "rust-src" ]; --- -2.52.0 - 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 new file mode 100644 index 0000000..7bf9a02 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0010-Log-policy-server-rejected-events.patch @@ -0,0 +1,31 @@ +From 2fdf69e5d068f20dfaf31097d05f2ba72a8bd374 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0010-nix-Temporarily-disable-go-in-flake.patch b/packages/overlays/matrix-synapse/patches/0010-nix-Temporarily-disable-go-in-flake.patch deleted file mode 100644 index 69a748e..0000000 --- a/packages/overlays/matrix-synapse/patches/0010-nix-Temporarily-disable-go-in-flake.patch +++ /dev/null @@ -1,26 +0,0 @@ -From d4f3c5be4922dd80fd5f568909bb87a7f8b63585 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Sat, 16 Aug 2025 20:18:45 +0200 -Subject: [PATCH 10/25] nix: Temporarily disable go in flake - -Signed-off-by: Rory& ---- - flake.nix | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/flake.nix b/flake.nix -index 5bde1e6c07..cf7a731f51 100644 ---- a/flake.nix -+++ b/flake.nix -@@ -151,7 +151,7 @@ - # * Perl is needed to run the SyTest test suite. - # * Rust is used for developing and running Synapse. - # It is installed manually with `packages` above. -- languages.go.enable = true; -+ #languages.go.enable = true; - languages.perl.enable = true; - - # Postgres is needed to run Synapse with postgres support and --- -2.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0011-Add-test-script.patch b/packages/overlays/matrix-synapse/patches/0011-Add-test-script.patch deleted file mode 100644 index 42578d6..0000000 --- a/packages/overlays/matrix-synapse/patches/0011-Add-test-script.patch +++ /dev/null @@ -1,22 +0,0 @@ -From a9691bcff0268ceaf0f104ebfae2ec87264eaabd Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Thu, 13 Nov 2025 13:56:59 +0100 -Subject: [PATCH 11/25] Add test script - -Signed-off-by: Rory& ---- - scripts-dev/run-tests.sh | 2 ++ - 1 file changed, 2 insertions(+) - create mode 100755 scripts-dev/run-tests.sh - -diff --git a/scripts-dev/run-tests.sh b/scripts-dev/run-tests.sh -new file mode 100755 -index 0000000000..1ac82801b2 ---- /dev/null -+++ b/scripts-dev/run-tests.sh -@@ -0,0 +1,2 @@ -+#! /usr/bin/env sh -+poetry run trial -j`nproc` tests --- -2.52.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 new file mode 100644 index 0000000..0bdfbdf --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0011-Use-parse_boolean-for-unredacted-content.patch @@ -0,0 +1,30 @@ +From ef028691294d2b6b15af1d2225dd2a777ec28cc4 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 5e7dcb0191..dac466f465 100644 +--- a/synapse/rest/client/room.py ++++ b/synapse/rest/client/room.py +@@ -1023,10 +1023,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.52.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 new file mode 100644 index 0000000..514f817 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0012-Expose-tombstone-in-room-admin-api.patch @@ -0,0 +1,115 @@ +From 94b2da38a5ab6200a7c7bc4a5e50a327f5aa128d 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 dac466f465..d28be2befb 100644 +--- a/synapse/rest/client/room.py ++++ b/synapse/rest/client/room.py +@@ -1027,7 +1027,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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0012-Fix-gitignore-to-ignore-.venv.patch b/packages/overlays/matrix-synapse/patches/0012-Fix-gitignore-to-ignore-.venv.patch deleted file mode 100644 index 8cb9746..0000000 --- a/packages/overlays/matrix-synapse/patches/0012-Fix-gitignore-to-ignore-.venv.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 1bd0f1088e49df63630d5a16e4607da4e6a7ab82 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Mon, 9 Jun 2025 17:46:10 +0200 -Subject: [PATCH 12/25] Fix gitignore to ignore .venv - -Signed-off-by: Rory& ---- - .gitignore | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/.gitignore b/.gitignore -index e333f2320b..3aec96e75e 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -30,6 +30,7 @@ __pycache__/ - /*.signing.key - /env/ - /.venv*/ -+/.venv - /homeserver*.yaml - /logs - /media_store/ --- -2.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0013-Fast-auth-links.patch b/packages/overlays/matrix-synapse/patches/0013-Fast-auth-links.patch deleted file mode 100644 index 3d88dd6..0000000 --- a/packages/overlays/matrix-synapse/patches/0013-Fast-auth-links.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 4d43d4b805789a8800ff8f0321ebe13432c0c7cf Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 22 Jul 2025 05:07:01 +0200 -Subject: [PATCH 13/25] Fast auth links - -Signed-off-by: Rory& ---- - synapse/storage/database.py | 43 +++++++++++++++++++ - .../databases/main/event_federation.py | 8 ++-- - 2 files changed, 47 insertions(+), 4 deletions(-) - -diff --git a/synapse/storage/database.py b/synapse/storage/database.py -index 6e38b55686..2bab1e53c5 100644 ---- a/synapse/storage/database.py -+++ b/synapse/storage/database.py -@@ -2608,6 +2608,49 @@ class DatabasePool: - - return txn.fetchall() - -+# requires database_engine.supports_using_any_list to be true -+def make_select_id_if_found_sql_clause( -+ database_engine: BaseDatabaseEngine, -+ column: str, -+ table: str, -+ iterable: Collection[Any], -+ *, -+ negative: bool = False, -+) -> tuple[str, list]: -+ """Returns an SQL clause that checks the given column is in the iterable. -+ -+ On SQLite this expands to `column IN (?, ?, ...)`, whereas on Postgres -+ it expands to `column = ANY(?)`. While both DBs support the `IN` form, -+ using the `ANY` form on postgres means that it views queries with -+ different length iterables as the same, helping the query stats. -+ -+ Args: -+ database_engine -+ column: Name of the column -+ table: Name of the table -+ iterable: The values to check the column against. -+ negative: Whether we should check for inequality, i.e. `NOT IN` -+ -+ Returns: -+ A tuple of SQL query and the args -+ """ -+ # This should hopefully be faster, but also makes postgres query -+ # stats easier to understand. -+ if database_engine.supports_using_any_list: -+ if not negative: -+ clause = f"{column}_lookup AS {column} FROM UNNEST(?::bigint[]) {column}_lookup WHERE EXISTS(SELECT FROM {table} WHERE {column}={column}_lookup)" -+ else: -+ clause = f"{column}_lookup AS {column} FROM UNNEST(?::bigint[]) {column}_lookup WHERE NOT EXISTS(SELECT FROM {table} WHERE {column}={column}_lookup)" -+ -+ return clause, [list(iterable)] -+ else: -+ params = ",".join("?" for _ in iterable) -+ if not negative: -+ clause = f"DISTINCT {column} FROM {table} WHERE {column} IN ({params})" -+ else: -+ clause = f"DISTINCT {column} FROM {table} WHERE {column} NOT IN ({params})" -+ return clause, list(iterable) -+ - - def make_in_list_sql_clause( - database_engine: BaseDatabaseEngine, -diff --git a/synapse/storage/databases/main/event_federation.py b/synapse/storage/databases/main/event_federation.py -index cc7083b605..55a0714f14 100644 ---- a/synapse/storage/databases/main/event_federation.py -+++ b/synapse/storage/databases/main/event_federation.py -@@ -47,6 +47,7 @@ from synapse.storage.database import ( - DatabasePool, - LoggingDatabaseConnection, - LoggingTransaction, -+ make_select_id_if_found_sql_clause, - ) - from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore - from synapse.storage.databases.main.events_worker import EventsWorkerStore -@@ -384,8 +385,7 @@ class EventFederationWorkerStore( - sql = """ - WITH RECURSIVE links(chain_id) AS ( - SELECT -- DISTINCT origin_chain_id -- FROM event_auth_chain_links WHERE %s -+ %s - UNION - SELECT - target_chain_id -@@ -402,8 +402,8 @@ class EventFederationWorkerStore( - while chains_to_fetch: - batch2 = tuple(itertools.islice(chains_to_fetch, 1000)) - chains_to_fetch.difference_update(batch2) -- clause, args = make_in_list_sql_clause( -- txn.database_engine, "origin_chain_id", batch2 -+ clause, args = make_select_id_if_found_sql_clause( -+ txn.database_engine, "origin_chain_id", "event_auth_chain_links", batch2 - ) - txn.execute(sql % (clause,), args) - --- -2.52.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 new file mode 100644 index 0000000..535a353 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0013-fix-Always-recheck-messages-pagination-data-if-a-bac.patch @@ -0,0 +1,196 @@ +From 5cd230419f6dc712ad6f0b931f7009f7fe0f0148 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0014-Add-too-much-logging-to-room-summary-over-federation.patch b/packages/overlays/matrix-synapse/patches/0014-Add-too-much-logging-to-room-summary-over-federation.patch deleted file mode 100644 index c89ddbf..0000000 --- a/packages/overlays/matrix-synapse/patches/0014-Add-too-much-logging-to-room-summary-over-federation.patch +++ /dev/null @@ -1,77 +0,0 @@ -From 0a2dc85a4e9e1c492b7d399fedd1dcb32511383f Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Wed, 23 Apr 2025 17:53:52 +0200 -Subject: [PATCH 14/25] Add too much logging to room summary over federation - -Signed-off-by: Rory& ---- - synapse/handlers/room_summary.py | 40 ++++++++++++++++++++++++++++---- - 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 ---- a/synapse/handlers/room_summary.py -+++ b/synapse/handlers/room_summary.py -@@ -748,23 +748,55 @@ class RoomSummaryHandler: - """ - # The API doesn't return the room version so assume that a - # join rule of knock is valid. -+ join_rule = room.get("join_rule") -+ world_readable = room.get("world_readable") -+ -+ logger.warning( -+ "[EMMA] Checking if room %s is accessible to %s: join_rule=%s, world_readable=%s", -+ room_id, requester, join_rule, world_readable -+ ) -+ - if ( -- room.get("join_rule", JoinRules.PUBLIC) -- in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED) -- or room.get("world_readable") is True -+ join_rule in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED) -+ or world_readable is True - ): - return True -- elif not requester: -+ else: -+ logger.warning( -+ "[EMMA] Room %s is not accessible to %s: join_rule=%s, world_readable=%s, join_rule result=%s, world_readable result=%s", -+ room_id, requester, join_rule, world_readable, -+ join_rule in (JoinRules.PUBLIC, JoinRules.KNOCK, JoinRules.KNOCK_RESTRICTED), -+ world_readable is True -+ ) -+ -+ if not requester: -+ logger.warning( -+ "[EMMA] No requester, so room %s is not accessible", -+ room_id -+ ) - return False -+ - - # Check if the user is a member of any of the allowed rooms from the response. - allowed_rooms = room.get("allowed_room_ids") -+ logger.warning( -+ "[EMMA] Checking if room %s is in allowed rooms for %s: join_rule=%s, allowed_rooms=%s", -+ requester, -+ room_id, -+ join_rule, -+ allowed_rooms -+ ) - if allowed_rooms and isinstance(allowed_rooms, list): - if await self._event_auth_handler.is_user_in_rooms( - allowed_rooms, requester - ): - return True - -+ logger.warning( -+ "[EMMA] Checking if room %s is accessble to %s via local state", -+ room_id, -+ requester -+ ) - # Finally, check locally if we can access the room. The user might - # already be in the room (if it was a child room), or there might be a - # pending invite, etc. --- -2.52.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 new file mode 100644 index 0000000..89e812e --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0014-Fix-pagination-with-large-gaps-of-rejected-events.patch @@ -0,0 +1,51 @@ +From 4775b12430b2fd8c7487f3779087cd16ece63bf7 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0015-Log-entire-room-if-accessibility-check-fails.patch b/packages/overlays/matrix-synapse/patches/0015-Log-entire-room-if-accessibility-check-fails.patch deleted file mode 100644 index d471056..0000000 --- a/packages/overlays/matrix-synapse/patches/0015-Log-entire-room-if-accessibility-check-fails.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 44797ee743422f0e5658b21c97c8e558838a1ca8 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Wed, 23 Apr 2025 18:24:57 +0200 -Subject: [PATCH 15/25] Log entire room if accessibility check fails - -Signed-off-by: Rory& ---- - synapse/handlers/room_summary.py | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/synapse/handlers/room_summary.py b/synapse/handlers/room_summary.py -index 5ea32af620..30ee91cd95 100644 ---- a/synapse/handlers/room_summary.py -+++ b/synapse/handlers/room_summary.py -@@ -964,6 +964,10 @@ class RoomSummaryHandler: - if not room_entry or not await self._is_remote_room_accessible( - requester, room_entry.room_id, room_entry.room - ): -+ logger.warning( -+ "[Emma] Room entry contents: %s", -+ room_entry.room if room_entry else None -+ ) - raise NotFoundError("Room not found or is not accessible") - - room = dict(room_entry.room) --- -2.52.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 new file mode 100644 index 0000000..635e1a6 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0015-RequestRatelimiter-expose-can_do_action.patch @@ -0,0 +1,96 @@ +From edb8616c493500b52f509a8c27ee2d62ebfae758 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.52.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 new file mode 100644 index 0000000..ec49b23 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0016-Clarify-pre_event_ids-assert-in-event-creation-handl.patch @@ -0,0 +1,26 @@ +From dd61c685748369af6e7b46dd55ea4f5b8d4447b7 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 99ce120736..0c7bf8b4d1 100644 +--- a/synapse/handlers/message.py ++++ b/synapse/handlers/message.py +@@ -1274,7 +1274,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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0016-Log-policy-server-rejected-events.patch b/packages/overlays/matrix-synapse/patches/0016-Log-policy-server-rejected-events.patch deleted file mode 100644 index b8721ef..0000000 --- a/packages/overlays/matrix-synapse/patches/0016-Log-policy-server-rejected-events.patch +++ /dev/null @@ -1,31 +0,0 @@ -From f1a71014702672957fc725e4868da89e98cd0ca9 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 05:21:46 +0200 -Subject: [PATCH 16/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.52.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 new file mode 100644 index 0000000..04f7835 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0017-Add-bulk-send-events-endpoint.patch @@ -0,0 +1,196 @@ +From a96f2eb4361749a083b0c8a49068af8154333232 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 baff999ab0..5a85a415e6 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 d28be2befb..7bcfdb68ce 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 +@@ -45,6 +47,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, +@@ -52,6 +55,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, +@@ -486,7 +490,6 @@ class RoomSendEventRestServlet(TransactionRestServlet): + txn_id, + ) + +- + def _parse_request_delay( + request: SynapseRequest, + max_delay: int | None, +@@ -1728,6 +1731,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) +@@ -1737,6 +1846,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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0017-Use-parse_boolean-for-unredacted-content.patch b/packages/overlays/matrix-synapse/patches/0017-Use-parse_boolean-for-unredacted-content.patch deleted file mode 100644 index 42b38d5..0000000 --- a/packages/overlays/matrix-synapse/patches/0017-Use-parse_boolean-for-unredacted-content.patch +++ /dev/null @@ -1,30 +0,0 @@ -From df76199739b64d4f461cd73c97438417319188f1 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 06:14:26 +0200 -Subject: [PATCH 17/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 5e7dcb0191..dac466f465 100644 ---- a/synapse/rest/client/room.py -+++ b/synapse/rest/client/room.py -@@ -1023,10 +1023,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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0018-Expose-tombstone-in-room-admin-api.patch b/packages/overlays/matrix-synapse/patches/0018-Expose-tombstone-in-room-admin-api.patch deleted file mode 100644 index 01af09a..0000000 --- a/packages/overlays/matrix-synapse/patches/0018-Expose-tombstone-in-room-admin-api.patch +++ /dev/null @@ -1,115 +0,0 @@ -From ffebf97a7aac3b174091eb02fbc180eda261b17f Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 27 May 2025 06:37:52 +0200 -Subject: [PATCH 18/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 dac466f465..d28be2befb 100644 ---- a/synapse/rest/client/room.py -+++ b/synapse/rest/client/room.py -@@ -1027,7 +1027,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.52.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 new file mode 100644 index 0000000..63a5ba0 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0018-admin-api-send-more-data.patch @@ -0,0 +1,170 @@ +From 1082cc5441ca3153016c456b89b07ce7846525e2 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 5a85a415e6..3326ead0d5 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.52.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 new file mode 100644 index 0000000..3b7019a --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0019-Allow-overriding-max-background-task-count.patch @@ -0,0 +1,52 @@ +From 885576e816a6f888a7e294a717362f630c75cc6a 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 e5cfc85a37..2aaf7a017f 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0019-fix-Always-recheck-messages-pagination-data-if-a-bac.patch b/packages/overlays/matrix-synapse/patches/0019-fix-Always-recheck-messages-pagination-data-if-a-bac.patch deleted file mode 100644 index 3197084..0000000 --- a/packages/overlays/matrix-synapse/patches/0019-fix-Always-recheck-messages-pagination-data-if-a-bac.patch +++ /dev/null @@ -1,196 +0,0 @@ -From 411dee16d58e9fa461b5a3883acf6fe68821cb36 Mon Sep 17 00:00:00 2001 -From: Jason Little -Date: Wed, 30 Apr 2025 09:29:42 -0500 -Subject: [PATCH 19/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 7808f8928b..7131a7ae3d 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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0020-Fix-pagination-with-large-gaps-of-rejected-events.patch b/packages/overlays/matrix-synapse/patches/0020-Fix-pagination-with-large-gaps-of-rejected-events.patch deleted file mode 100644 index fae9e5a..0000000 --- a/packages/overlays/matrix-synapse/patches/0020-Fix-pagination-with-large-gaps-of-rejected-events.patch +++ /dev/null @@ -1,51 +0,0 @@ -From a17498c26ea8e729f2cdcda7e7c8ae1be46db011 Mon Sep 17 00:00:00 2001 -From: Nicolas Werner -Date: Sun, 8 Jun 2025 23:14:31 +0200 -Subject: [PATCH 20/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.52.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 new file mode 100644 index 0000000..3fa292e --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0020-max-and-min-were-probably-switched.-Set-max-to-arbit.patch @@ -0,0 +1,39 @@ +From bf1ab7b6b7bdfda9329ca1952b244162edf90aa9 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0021-RequestRatelimiter-expose-can_do_action.patch b/packages/overlays/matrix-synapse/patches/0021-RequestRatelimiter-expose-can_do_action.patch deleted file mode 100644 index 0639e19..0000000 --- a/packages/overlays/matrix-synapse/patches/0021-RequestRatelimiter-expose-can_do_action.patch +++ /dev/null @@ -1,96 +0,0 @@ -From c0fdf986b551c9511e49111ecc5660ccc1bb0ae0 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Fri, 25 Jul 2025 08:26:15 +0200 -Subject: [PATCH 21/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.52.0 - diff --git a/packages/overlays/matrix-synapse/patches/0021-changelog.patch b/packages/overlays/matrix-synapse/patches/0021-changelog.patch new file mode 100644 index 0000000..40cf414 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0021-changelog.patch @@ -0,0 +1,20 @@ +From 5608626043f1a410b1ec426f58e77a7259f68186 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0022-Clarify-pre_event_ids-assert-in-event-creation-handl.patch b/packages/overlays/matrix-synapse/patches/0022-Clarify-pre_event_ids-assert-in-event-creation-handl.patch deleted file mode 100644 index aef2fed..0000000 --- a/packages/overlays/matrix-synapse/patches/0022-Clarify-pre_event_ids-assert-in-event-creation-handl.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 568c93e969534eaf29997aa38efb4ac35f493617 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Sat, 16 Aug 2025 20:19:08 +0200 -Subject: [PATCH 22/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 99ce120736..0c7bf8b4d1 100644 ---- a/synapse/handlers/message.py -+++ b/synapse/handlers/message.py -@@ -1274,7 +1274,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.52.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 new file mode 100644 index 0000000..db9f06b --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0022-Update-changelog.d-19394.bugfix.patch @@ -0,0 +1,20 @@ +From b4eaaae1102b713bae9799b5655e07eb43966550 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0023-Add-bulk-send-events-endpoint.patch b/packages/overlays/matrix-synapse/patches/0023-Add-bulk-send-events-endpoint.patch deleted file mode 100644 index 99c6b5e..0000000 --- a/packages/overlays/matrix-synapse/patches/0023-Add-bulk-send-events-endpoint.patch +++ /dev/null @@ -1,196 +0,0 @@ -From f004f0bb0a88ced5db426b0eacbc3c80302d59a2 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Sat, 26 Jul 2025 09:50:56 +0200 -Subject: [PATCH 23/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 baff999ab0..5a85a415e6 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 d28be2befb..7bcfdb68ce 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 -@@ -45,6 +47,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, -@@ -52,6 +55,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, -@@ -486,7 +490,6 @@ class RoomSendEventRestServlet(TransactionRestServlet): - txn_id, - ) - -- - def _parse_request_delay( - request: SynapseRequest, - max_delay: int | None, -@@ -1728,6 +1731,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) -@@ -1737,6 +1846,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.52.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 new file mode 100644 index 0000000..f965483 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0023-Adjust-for-the-retry-interval-actually-being-a-timeo.patch @@ -0,0 +1,257 @@ +From 13ccc96c9a41e4a80375e9be0b288d6ef9711630 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0024-admin-api-send-more-data.patch b/packages/overlays/matrix-synapse/patches/0024-admin-api-send-more-data.patch deleted file mode 100644 index 2689260..0000000 --- a/packages/overlays/matrix-synapse/patches/0024-admin-api-send-more-data.patch +++ /dev/null @@ -1,170 +0,0 @@ -From f859e4a53860ff3129042872968a826e42d4e6f0 Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Mon, 27 Oct 2025 19:23:42 +0100 -Subject: [PATCH 24/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 5a85a415e6..3326ead0d5 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.52.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 new file mode 100644 index 0000000..3a75bc6 --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0024-unecessarily-long-pump-in-test-left-over-from-testin.patch @@ -0,0 +1,26 @@ +From 6a18e62bcf4d1bd21b4105495c10f5deaf8f902a 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.52.0 + diff --git a/packages/overlays/matrix-synapse/patches/0025-Allow-overriding-max-background-task-count.patch b/packages/overlays/matrix-synapse/patches/0025-Allow-overriding-max-background-task-count.patch deleted file mode 100644 index c5a0310..0000000 --- a/packages/overlays/matrix-synapse/patches/0025-Allow-overriding-max-background-task-count.patch +++ /dev/null @@ -1,52 +0,0 @@ -From e5d0a37b36c43c396356c72563f448df799bfc6c Mon Sep 17 00:00:00 2001 -From: Rory& -Date: Tue, 28 Oct 2025 00:01:45 +0100 -Subject: [PATCH 25/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 e5cfc85a37..2aaf7a017f 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.52.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 new file mode 100644 index 0000000..fabaeff --- /dev/null +++ b/packages/overlays/matrix-synapse/patches/0025-adjust-changelog-again.patch @@ -0,0 +1,19 @@ +From facc7fb4494360dcd96099fcbdb223c5933e6321 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.52.0 + -- cgit 1.5.1