From b3a49eac79f6d802ae5be9582880705fe0528a8f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Tue, 11 Nov 2025 16:04:56 +0000 Subject: [PATCH] Try to update to support per-user enablement --- synapse/config/experimental.py | 2 +- synapse/federation/sender/__init__.py | 2 - .../sender/per_destination_queue.py | 48 +++++++++---------- synapse/handlers/sync.py | 4 +- synapse/rest/admin/experimental_features.py | 3 ++ synapse/rest/client/versions.py | 5 +- .../databases/main/experimental_features.py | 2 +- synapse/visibility.py | 4 +- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 5f6797ecf8..b6e36f6600 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -595,4 +595,4 @@ class ExperimentalConfig(Config): self.msc4306_enabled: bool = experimental.get("msc4306_enabled", False) # MSC4354: Sticky Events - self.msc4354_enabled: bool = experimental.get("msc4354_enabled", False) + self.msc4354_enabled_value: bool = experimental.get("msc4354_enabled", False) diff --git a/synapse/federation/sender/__init__.py b/synapse/federation/sender/__init__.py index 16bfcdb9ef..f315c69676 100644 --- a/synapse/federation/sender/__init__.py +++ b/synapse/federation/sender/__init__.py @@ -511,8 +511,6 @@ class FederationSender(AbstractFederationSender): def notify_new_server_joined(self, server: str, room_id: str) -> None: # We currently only use this notification for MSC4354: Sticky Events. - if not self.hs.config.experimental.msc4354_enabled: - return # fire off a processing loop in the background self.hs.run_as_background_process( "process_new_server_joined_over_federation", diff --git a/synapse/federation/sender/per_destination_queue.py b/synapse/federation/sender/per_destination_queue.py index ccbe8d07ae..fe74784c95 100644 --- a/synapse/federation/sender/per_destination_queue.py +++ b/synapse/federation/sender/per_destination_queue.py @@ -104,7 +104,6 @@ class PerDestinationQueue: self._instance_name = hs.get_instance_name() self._federation_shard_config = hs.config.worker.federation_shard_config self._state = hs.get_state_handler() - self.msc4354_enabled = hs.config.experimental.msc4354_enabled self._should_send_on_this_instance = True if not self._federation_shard_config.should_handle( @@ -582,32 +581,31 @@ class PerDestinationQueue: # send. extrem_events = await self._store.get_events_as_list(extrems) - if self.msc4354_enabled: - # we also want to send sticky events that are still active in this room - sticky_event_ids = ( - await self._store.get_sticky_event_ids_sent_by_self( - pdu.room_id, - last_successful_stream_ordering, - ) + # we also want to send sticky events that are still active in this room + sticky_event_ids = ( + await self._store.get_sticky_event_ids_sent_by_self( + pdu.room_id, + last_successful_stream_ordering, ) - # skip any that are actually the forward extremities we want to send anyway - sticky_events = await self._store.get_events_as_list( - [ - event_id - for event_id in sticky_event_ids - if event_id not in extrems - ] + ) + # skip any that are actually the forward extremities we want to send anyway + sticky_events = await self._store.get_events_as_list( + [ + event_id + for event_id in sticky_event_ids + if event_id not in extrems + ] + ) + if sticky_events: + # *prepend* these to the extrem list, so they are processed first. + # This ensures they will show up before the forward extrem in stream order + extrem_events = sticky_events + extrem_events + logger.info( + "Sending %d missed sticky events to %s: %r", + len(sticky_events), + self._destination, + pdu.room_id, ) - if sticky_events: - # *prepend* these to the extrem list, so they are processed first. - # This ensures they will show up before the forward extrem in stream order - extrem_events = sticky_events + extrem_events - logger.info( - "Sending %d missed sticky events to %s: %r", - len(sticky_events), - self._destination, - pdu.room_id, - ) new_pdus = [] for p in extrem_events: diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py index a3e7987da7..b16982d255 100644 --- a/synapse/handlers/sync.py +++ b/synapse/handlers/sync.py @@ -82,6 +82,7 @@ from synapse.util.caches.lrucache import LruCache from synapse.util.caches.response_cache import ResponseCache, ResponseCacheContext from synapse.util.metrics import Measure from synapse.visibility import filter_events_for_client +from synapse.rest.admin.experimental_features import ExperimentalFeature if TYPE_CHECKING: from synapse.server import HomeServer @@ -2178,6 +2179,7 @@ class SyncHandler: since_token = sync_result_builder.since_token user_id = sync_result_builder.sync_config.user.to_string() + calculate_sticky_events = self.store.is_feature_enabled(user_id, ExperimentalFeature.MSC4354) blocks_all_rooms = ( sync_result_builder.sync_config.filter_collection.blocks_all_rooms() @@ -2216,7 +2218,7 @@ class SyncHandler: sync_result_builder.now_token = now_token sticky_by_room: Dict[str, Set[str]] = {} - if self.hs_config.experimental.msc4354_enabled: + if await calculate_sticky_events: now_token, sticky_by_room = await self.sticky_events_by_room( sync_result_builder, now_token, since_token ) diff --git a/synapse/rest/admin/experimental_features.py b/synapse/rest/admin/experimental_features.py index abdb937793..b729acb76b 100644 --- a/synapse/rest/admin/experimental_features.py +++ b/synapse/rest/admin/experimental_features.py @@ -44,6 +44,7 @@ class ExperimentalFeature(str, Enum): MSC3881 = "msc3881" MSC3575 = "msc3575" MSC4222 = "msc4222" + MSC4354 = "msc4354" def is_globally_enabled(self, config: "HomeServerConfig") -> bool: if self is ExperimentalFeature.MSC3881: @@ -52,6 +53,8 @@ class ExperimentalFeature(str, Enum): return config.experimental.msc3575_enabled if self is ExperimentalFeature.MSC4222: return config.experimental.msc4222_enabled + if self is ExperimentalFeature.MSC4354: + return config.experimental.msc4354_enabled assert_never(self) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index c0f96419f7..ce3384f300 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -81,6 +81,9 @@ class VersionsRestServlet(RestServlet): msc3575_enabled = await self.store.is_feature_enabled( user_id, ExperimentalFeature.MSC3575 ) + msc4354_enabled = await self.store.is_feature_enabled( + user_id, ExperimentalFeature.MSC4354 + ) return ( 200, @@ -183,7 +186,7 @@ class VersionsRestServlet(RestServlet): # MSC4169: Backwards-compatible redaction sending using `/send` "com.beeper.msc4169": self.config.experimental.msc4169_enabled, # MSC4354: Sticky events - "org.matrix.msc4354": self.config.experimental.msc4354_enabled, + "org.matrix.msc4354": msc4354_enabled, }, }, ) diff --git a/synapse/storage/databases/main/experimental_features.py b/synapse/storage/databases/main/experimental_features.py index 77b6c36884..b8f9a022bc 100644 --- a/synapse/storage/databases/main/experimental_features.py +++ b/synapse/storage/databases/main/experimental_features.py @@ -114,7 +114,7 @@ class ExperimentalFeaturesStore(CacheInvalidationWorkerStore): if feature.is_globally_enabled(self.hs.config): return True - +msc4354_enabled # if it's not enabled globally, check if it is enabled per-user res = await self.db_pool.simple_select_one_onecol( table="per_user_experimental_features", diff --git a/synapse/visibility.py b/synapse/visibility.py index 5c59871f34..fccf29e69b 100644 --- a/synapse/visibility.py +++ b/synapse/visibility.py @@ -50,6 +50,7 @@ from synapse.types import ( ) from synapse.types.state import StateFilter from synapse.util.clock import Clock +from synapse.rest.admin.experimental_features import ExperimentalFeature logger = logging.getLogger(__name__) filtered_event_logger = logging.getLogger("synapse.visibility.filtered_event_debug") @@ -110,6 +111,7 @@ async def filter_events_for_client( # We copy the events list to guarantee any modifications we make will only # happen within the function. events_before_filtering = events.copy() + sticky_events_enabled = await storage.main.is_feature_enabled(user_id, ExperimentalFeature.MSC4354) # Default case is to *exclude* soft-failed events events = [e for e in events if not e.internal_metadata.is_soft_failed()] client_config = await storage.main.get_admin_client_config_for_user(user_id) @@ -203,7 +205,7 @@ async def filter_events_for_client( # to the cache! cloned = clone_event(filtered) cloned.unsigned[EventUnsignedContentFields.MEMBERSHIP] = user_membership - if storage.main.config.experimental.msc4354_enabled: + if sticky_events_enabled: sticky_duration = cloned.sticky_duration() if sticky_duration: now = storage.main.clock.time_msec()