Port find_relevant_room_ids_for_extension to Rust

`rust/src/sliding_sync/extensions/relevant.rs` holds the matching of an
extension's `lists` and `rooms` against the response. Phase 5 uses it,
and it is cheap to port now. A list without exactly one `SYNC`
operation fails, as Python's assertion does: that is a list with no
ranges or several, under an extension that names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L51DoxB8VnH8UjXJFDgpNG
This commit is contained in:
Erik JohnstonandClaude Opus 5.5 committed 2026-10-16 11:43:54 +00:00
1 parent d8a93ec3b8
commit 4f4611dc58
5 files changed
+222

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
/*
* This file is licensed under the Affero General Public License (AGPL) version 3.
*
* Copyright (C) 2026 Element Creations Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* See the GNU Affero General Public License for more details:
* <https://www.gnu.org/licenses/agpl-3.0.html>.
*/
//! The sliding sync extensions (`synapse.handlers.sliding_sync.extensions`).
use pyo3::{prelude::*, types::PyModule};
pub mod relevant;
pub fn register_test_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
relevant::register_test_functions(m)
}
@@ -0,0 +1,120 @@
/*
* This file is licensed under the Affero General Public License (AGPL) version 3.
*
* Copyright (C) 2026 Element Creations Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* See the GNU Affero General Public License for more details:
* <https://www.gnu.org/licenses/agpl-3.0.html>.
*/
//! `find_relevant_room_ids_for_extension`: the rooms an extension covers,
//! from its `lists` and `rooms` and what the response holds.
use std::collections::HashSet;
use std::sync::Arc;
use anyhow::{bail, Context};
use indexmap::IndexMap;
use pyo3::{exceptions::PyAssertionError, prelude::*, types::PyModule};
use crate::sliding_sync::room_lists::lists::SyncOp;
/// The response's lists, by name, each with its operations.
pub type ActualLists = IndexMap<Arc<str>, Vec<SyncOp>>;
/// The rooms of a list's single `SYNC` operation. Python asserts there is
/// exactly one, so a list with no ranges, or several, fails here.
fn single_op_rooms<'a>(name: &str, ops: &'a [SyncOp]) -> anyhow::Result<&'a [Arc<str>]> {
match ops {
[op] => Ok(&op.room_ids),
_ => bail!("list {name} has {} operations, not one", ops.len()),
}
}
/// `find_relevant_room_ids_for_extension`: of the response, the rooms
/// `requested_room_ids` names among `actual_room_ids` (the room
/// subscriptions), and the rooms of the lists `requested_lists` names. `*`
/// means all of them.
pub fn find_relevant_room_ids_for_extension(
requested_lists: Option<&[String]>,
requested_room_ids: Option<&[String]>,
actual_lists: &ActualLists,
actual_room_ids: &HashSet<Arc<str>>,
) -> anyhow::Result<HashSet<Arc<str>>> {
let mut relevant: HashSet<Arc<str>> = HashSet::new();
for room_id in requested_room_ids.unwrap_or_default() {
if room_id == "*" {
relevant.extend(actual_room_ids.iter().cloned());
break;
}
if let Some(room_id) = actual_room_ids.get(room_id.as_str()) {
relevant.insert(Arc::clone(room_id));
}
}
for list_key in requested_lists.unwrap_or_default() {
if list_key == "*" {
for (name, ops) in actual_lists {
relevant.extend(single_op_rooms(name, ops)?.iter().cloned());
}
break;
}
if let Some(ops) = actual_lists.get(list_key.as_str()) {
relevant.extend(
single_op_rooms(list_key, ops)
.context("an extension's list")?
.iter()
.cloned(),
);
}
}
Ok(relevant)
}
/// `find_relevant_room_ids_for_extension` through the Rust implementation,
/// each list as its operations' room IDs. Raises `AssertionError` as
/// Python does.
#[pyfunction]
fn find_relevant_room_ids_for_test(
requested_lists: Option<Vec<String>>,
requested_room_ids: Option<Vec<String>>,
actual_lists: Vec<(String, Vec<Vec<String>>)>,
actual_room_ids: HashSet<String>,
) -> PyResult<Vec<String>> {
let actual_lists: ActualLists = actual_lists
.into_iter()
.map(|(name, ops)| {
let ops = ops
.into_iter()
.map(|room_ids| SyncOp {
range: (0, 0),
room_ids: room_ids.into_iter().map(Arc::from).collect(),
})
.collect();
(Arc::from(name), ops)
})
.collect();
let actual_room_ids: HashSet<Arc<str>> = actual_room_ids.into_iter().map(Arc::from).collect();
let relevant = find_relevant_room_ids_for_extension(
requested_lists.as_deref(),
requested_room_ids.as_deref(),
&actual_lists,
&actual_room_ids,
)
.map_err(|e| PyAssertionError::new_err(e.to_string()))?;
let mut relevant: Vec<String> = relevant.iter().map(|r| r.to_string()).collect();
relevant.sort();
Ok(relevant)
}
pub fn register_test_functions(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(find_relevant_room_ids_for_test, m)?)?;
Ok(())
}
+2
View File
@@ -38,6 +38,7 @@ use crate::storage::sync_store::{SyncDbStats, SyncStore};
pub mod config;
pub mod connection;
pub mod extensions;
pub mod request;
pub mod required_state;
pub mod room;
@@ -168,6 +169,7 @@ pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()>
child_module.add_class::<request::SlidingSyncRequest>()?;
config::register_test_functions(&child_module)?;
connection::register_test_functions(&child_module)?;
extensions::register_test_functions(&child_module)?;
required_state::register_test_functions(&child_module)?;
room::register_test_functions(&child_module)?;
room_lists::register_test_functions(&child_module)?;
+8
View File
@@ -240,3 +240,11 @@ def room_config_to_persist_for_test(
ignore_timeline_bound: bool,
limited: bool | None,
) -> tuple[int, dict[str, list[str]]] | None: ...
def find_relevant_room_ids_for_test(
requested_lists: list[str] | None,
requested_room_ids: list[str] | None,
actual_lists: list[tuple[str, list[list[str]]]],
actual_room_ids: set[str],
) -> list[str]:
"""Each list as its operations' room IDs. Raises `AssertionError` as
Python does."""
@@ -35,6 +35,7 @@ from synapse.handlers.sliding_sync import (
_stripped_state,
_timeline_bounds,
)
from synapse.handlers.sliding_sync.extensions import SlidingSyncExtensionHandler
from synapse.handlers.sliding_sync.room_lists import (
SlidingSyncRoomLists,
_sync_list_ops,
@@ -53,11 +54,13 @@ from synapse.types.handlers import SLIDING_SYNC_DEFAULT_BUMP_EVENT_TYPES
from synapse.types.handlers.sliding_sync import (
HaveSentRoom,
HaveSentRoomFlag,
OperationType,
PerConnectionState,
RoomLazyMembershipChanges,
RoomStatusMap,
RoomSyncConfig,
SlidingSyncConfig,
SlidingSyncResult,
StateValues,
)
from synapse.types.state import StateFilter
@@ -1210,3 +1213,69 @@ class RoomHelpersTestCase(unittest.TestCase):
(python.timeline_limit, _as_sets(python.required_state_map)),
context,
)
class RelevantRoomIdsTestCase(unittest.TestCase):
"""`rust/src/sliding_sync/extensions/relevant.rs`"""
def test_against_python(self) -> None:
rng = random.Random(18)
names = ["a", "b", "c", "*"]
raised = 0
for _ in range(2000):
actual_lists = [
(
name,
[
rng.sample(_ROOMS, rng.randint(0, 3))
for _ in range(rng.choice([1, 1, 1, 1, 0, 2]))
],
)
for name in rng.sample(names[:3], rng.randint(0, 3))
]
actual_room_ids = set(rng.sample(_ROOMS, rng.randint(0, 3)))
requested_lists = rng.choice(
[None, rng.sample(names + ["missing"], rng.randint(0, 3))]
)
requested_room_ids = rng.choice(
[None, rng.sample([*_ROOMS[:4], "*"], rng.randint(0, 3))]
)
try:
python: list[str] | None = sorted(
SlidingSyncExtensionHandler.find_relevant_room_ids_for_extension(
cast(SlidingSyncExtensionHandler, None),
requested_lists,
requested_room_ids,
{
name: SlidingSyncResult.SlidingWindowList(
count=0,
ops=[
SlidingSyncResult.SlidingWindowList.Operation(
op=OperationType.SYNC,
range=(0, 0),
room_ids=room_ids,
)
for room_ids in ops
],
)
for name, ops in actual_lists
},
actual_room_ids,
)
)
except AssertionError:
python = None
raised += 1
try:
result: list[str] | None = rust.find_relevant_room_ids_for_test(
requested_lists, requested_room_ids, actual_lists, actual_room_ids
)
except AssertionError:
result = None
self.assertEqual(
result,
python,
(requested_lists, requested_room_ids, actual_lists, actual_room_ids),
)
self.assertGreater(raised, 0)