mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-08 07:56:55 -04:00
fix(sdk): Prevent bugs, remove expensive clones, and simplify SlidingSyncList.
There are problems with `SlidingSyncListRequestGenerator`: * It's part of the module API, * It contains a clone of `SlidingSyncList`. To create a `SlidingSyncListRequestGenerator`, one has to call `SlidingSyncList::request_generator`. It was done in `SlidingSync::stream`. The problem is that it clones `SlidingSyncList`. So theoritically it is possible to create multiple request generators for the _same_ list, and use them to send many requests and to update the _same_ list with multiple responses. This is utterly error-prone and can lead to really complex bugs to discover. Moreover, it's a lot of clones. Cloning a `SlidingSyncListRequestGenerator` isn't cheap as it means cloning a `SlidingSyncList`. Moreover, cloning a `SlidingSyncList` isn't cheap as it means cloning the entire struct. Having `SlidingSyncListRequestGenerator` inside the module API also makes the code of `SlidingSync` more complex (why having to deal with lists and request generators at the same time? we must be very careful to maintain both side by side? what if a list is removed but not its request generator? and so on). So. This patch simplifies all that. First off, it extracts all the fields of `SlidingSyncList` into a `SlidingSyncListInner` struct. Then, `SlidingSyncList` has only one field: `Arc<SlidingSyncListInner>`. Boom, it's now cheap to clone it. Second, `SlidingSyncListRequestGenerator` is only a struct with constructors, but there is no extra methods. `SlidingSyncListRequestGenerator` _no longer_ contains a `SlidingSyncList`, and doesn't no need a list at all to work. It's just values. Third, `SlidingSyncList` holds a `SlidingSyncListRequestGenerator`, and only one. Fourth, `SlidingSyncList` (and `SlidingSyncListInner`) now has methods to handle the internal request generator. The initial `impl Iterator for SlidingSyncListRequestGenerator` becomes a simple `SlidingSyncList::next_request` method. Fifth, previously, the `SlidingSyncList::handle_response` was never called directly by `SlidingSync`. It was called by `SlidingSyncListRequestGenerator`! How confusing! `SlidingSync` called `SlidingSyncListRequestGenerator::handle_response` which was calling `SlidingSyncList::handle_response`. Now, the flow is more natural: `SlidingSync` calls `SlidingSyncList::handle_response` and that's it. Sixth, the `SlidingSyncList::handle_response` is now composed of 2 parts: updating the list itself, and updating the request generator. It was kind of the case before, but onto two different types. It was unclear which types were updating `SlidingSyncList`. For example, `SlidingSyncList::state` was updated by… `SlidingSyncListRequestGenerator`. Now `SlidingSyncList` is responsible to update itself, and no one else. Finally, `SlidingSync` no longer have to deal with `SlidingSyncListRequestGenerator`. All it has is a set of `SlidingSyncList`, and that's it! The tests are still passing, hurray.
This commit is contained in:
@@ -104,7 +104,7 @@ impl SlidingSyncBuilder {
|
||||
///
|
||||
/// Replace any list with the name.
|
||||
pub fn add_list(mut self, list: SlidingSyncList) -> Self {
|
||||
self.lists.insert(list.name.clone(), list);
|
||||
self.lists.insert(list.name().to_owned(), list);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use eyeball::unique::Observable;
|
||||
use eyeball_im::ObservableVector;
|
||||
use ruma::{api::client::sync::sync_events::v4, events::StateEventType, UInt};
|
||||
|
||||
use super::{Error, SlidingSyncList, SlidingSyncMode, SlidingSyncState};
|
||||
use super::{
|
||||
Error, SlidingSyncList, SlidingSyncListInner, SlidingSyncListRequestGenerator, SlidingSyncMode,
|
||||
SlidingSyncState,
|
||||
};
|
||||
use crate::Result;
|
||||
|
||||
/// The default name for the full sync list.
|
||||
@@ -147,28 +150,52 @@ impl SlidingSyncListBuilder {
|
||||
|
||||
/// Build the list.
|
||||
pub fn build(self) -> Result<SlidingSyncList> {
|
||||
Ok(SlidingSyncList {
|
||||
//
|
||||
// From the builder
|
||||
sync_mode: self.sync_mode,
|
||||
sort: self.sort,
|
||||
required_state: self.required_state,
|
||||
full_sync_batch_size: self.full_sync_batch_size,
|
||||
full_sync_maximum_number_of_rooms_to_fetch: self
|
||||
.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
send_updates_for_items: self.send_updates_for_items,
|
||||
filters: self.filters,
|
||||
timeline_limit: Arc::new(StdRwLock::new(Observable::new(self.timeline_limit))),
|
||||
name: self.name.ok_or(Error::BuildMissingField("name"))?,
|
||||
ranges: Arc::new(StdRwLock::new(Observable::new(self.ranges))),
|
||||
let request_generator = match &self.sync_mode {
|
||||
SlidingSyncMode::PagingFullSync => {
|
||||
SlidingSyncListRequestGenerator::new_paging_full_sync(
|
||||
self.full_sync_batch_size,
|
||||
self.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
// Default values for the type we are building.
|
||||
state: Arc::new(StdRwLock::new(Observable::new(SlidingSyncState::default()))),
|
||||
maximum_number_of_rooms: Arc::new(StdRwLock::new(Observable::new(None))),
|
||||
rooms_list: Arc::new(StdRwLock::new(ObservableVector::new())),
|
||||
is_cold: Arc::new(AtomicBool::new(false)),
|
||||
rooms_updated_broadcast: Arc::new(StdRwLock::new(Observable::new(()))),
|
||||
SlidingSyncMode::GrowingFullSync => {
|
||||
SlidingSyncListRequestGenerator::new_growing_full_sync(
|
||||
self.full_sync_batch_size,
|
||||
self.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
)
|
||||
}
|
||||
|
||||
SlidingSyncMode::Selective => SlidingSyncListRequestGenerator::new_selective(),
|
||||
};
|
||||
|
||||
Ok(SlidingSyncList {
|
||||
inner: Arc::new(SlidingSyncListInner {
|
||||
//
|
||||
// From the builder
|
||||
sync_mode: self.sync_mode,
|
||||
sort: self.sort,
|
||||
required_state: self.required_state,
|
||||
full_sync_batch_size: self.full_sync_batch_size,
|
||||
full_sync_maximum_number_of_rooms_to_fetch: self
|
||||
.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
send_updates_for_items: self.send_updates_for_items,
|
||||
filters: self.filters,
|
||||
timeline_limit: StdRwLock::new(Observable::new(self.timeline_limit)),
|
||||
name: self.name.ok_or(Error::BuildMissingField("name"))?,
|
||||
ranges: StdRwLock::new(Observable::new(self.ranges)),
|
||||
|
||||
//
|
||||
// Computed from the builder.
|
||||
request_generator: StdRwLock::new(request_generator),
|
||||
|
||||
//
|
||||
// Default values for the type we are building.
|
||||
state: StdRwLock::new(Observable::new(SlidingSyncState::default())),
|
||||
maximum_number_of_rooms: StdRwLock::new(Observable::new(None)),
|
||||
rooms_list: StdRwLock::new(ObservableVector::new()),
|
||||
is_cold: AtomicBool::new(false),
|
||||
rooms_updated_broadcast: StdRwLock::new(Observable::new(())),
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ mod builder;
|
||||
mod request_generator;
|
||||
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::BTreeMap,
|
||||
fmt::Debug,
|
||||
iter,
|
||||
@@ -17,9 +18,11 @@ use eyeball_im::{ObservableVector, VectorDiff};
|
||||
use futures_core::Stream;
|
||||
use imbl::Vector;
|
||||
pub(super) use request_generator::*;
|
||||
use ruma::{api::client::sync::sync_events::v4, events::StateEventType, OwnedRoomId, RoomId, UInt};
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v4, assign, events::StateEventType, OwnedRoomId, RoomId, UInt,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use tracing::{debug, error, instrument, warn};
|
||||
|
||||
use super::{Error, FrozenSlidingSyncRoom, SlidingSyncRoom};
|
||||
use crate::Result;
|
||||
@@ -40,15 +43,22 @@ use crate::Result;
|
||||
/// # anyhow::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// It is OK to clone this type as much as you need: cloning it is cheap.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SlidingSyncList {
|
||||
/// Which SlidingSyncMode to start this list under
|
||||
inner: Arc<SlidingSyncListInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct SlidingSyncListInner {
|
||||
/// Which [`SlidingSyncMode`] to start this list under.
|
||||
sync_mode: SlidingSyncMode,
|
||||
|
||||
/// Sort the rooms list by this
|
||||
/// Sort the rooms list by this.
|
||||
sort: Vec<String>,
|
||||
|
||||
/// Required states to return per room
|
||||
/// Required states to return per room.
|
||||
required_state: Vec<(StateEventType, String)>,
|
||||
|
||||
/// When doing a full-sync, the ranges of rooms to load are extended by this
|
||||
@@ -66,14 +76,14 @@ pub struct SlidingSyncList {
|
||||
/// Any filters to apply to the query.
|
||||
filters: Option<v4::SyncRequestListFilters>,
|
||||
|
||||
/// The maximum number of timeline events to query for
|
||||
pub timeline_limit: Arc<StdRwLock<Observable<Option<UInt>>>>,
|
||||
/// The maximum number of timeline events to query for.
|
||||
pub timeline_limit: StdRwLock<Observable<Option<UInt>>>,
|
||||
|
||||
/// Name of this list to easily recognize them
|
||||
/// Name of this list to easily recognize them.
|
||||
pub name: String,
|
||||
|
||||
/// The state this list is in.
|
||||
state: Arc<StdRwLock<Observable<SlidingSyncState>>>,
|
||||
state: StdRwLock<Observable<SlidingSyncState>>,
|
||||
|
||||
/// The total number of rooms that is possible to interact with for the
|
||||
/// given list.
|
||||
@@ -82,41 +92,37 @@ pub struct SlidingSyncList {
|
||||
/// client that it's possible to fetch this amount of rooms maximum.
|
||||
/// Since this number can change according to the list filters, it's
|
||||
/// observable.
|
||||
maximum_number_of_rooms: Arc<StdRwLock<Observable<Option<u32>>>>,
|
||||
maximum_number_of_rooms: StdRwLock<Observable<Option<u32>>>,
|
||||
|
||||
/// The rooms in order.
|
||||
rooms_list: Arc<StdRwLock<ObservableVector<RoomListEntry>>>,
|
||||
rooms_list: StdRwLock<ObservableVector<RoomListEntry>>,
|
||||
|
||||
/// The ranges windows of the list.
|
||||
#[allow(clippy::type_complexity)] // temporarily
|
||||
ranges: Arc<StdRwLock<Observable<Vec<(UInt, UInt)>>>>,
|
||||
ranges: StdRwLock<Observable<Vec<(UInt, UInt)>>>,
|
||||
|
||||
/// Get informed if anything in the room changed.
|
||||
///
|
||||
/// If you only care to know about changes once all of them have applied
|
||||
/// (including the total), subscribe to this observable.
|
||||
pub rooms_updated_broadcast: Arc<StdRwLock<Observable<()>>>,
|
||||
pub rooms_updated_broadcast: StdRwLock<Observable<()>>,
|
||||
|
||||
is_cold: Arc<AtomicBool>,
|
||||
is_cold: AtomicBool,
|
||||
|
||||
/// The request generator, i.e. a type that yields the appropriate list
|
||||
/// request. See [`SlidingSyncListRequestGenerator`] to learn more.
|
||||
request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
|
||||
}
|
||||
|
||||
impl SlidingSyncList {
|
||||
/// Generate a pre-configured [`SlidingSyncListRequestGenerator`] based on
|
||||
/// the current [`Self::sync_mode`].
|
||||
pub(super) fn request_generator(&self) -> SlidingSyncListRequestGenerator {
|
||||
match &self.sync_mode {
|
||||
SlidingSyncMode::PagingFullSync => {
|
||||
SlidingSyncListRequestGenerator::new_with_paging_full_sync(self.clone())
|
||||
}
|
||||
/// Get the name of the list.
|
||||
pub fn name(&self) -> &str {
|
||||
self.inner.name.as_str()
|
||||
}
|
||||
|
||||
SlidingSyncMode::GrowingFullSync => {
|
||||
SlidingSyncListRequestGenerator::new_with_growing_full_sync(self.clone())
|
||||
}
|
||||
|
||||
SlidingSyncMode::Selective => {
|
||||
SlidingSyncListRequestGenerator::new_selective(self.clone())
|
||||
}
|
||||
}
|
||||
/// Calculate the next request and return it.
|
||||
pub(super) fn next_request(&mut self) -> Option<v4::SyncRequestList> {
|
||||
self.inner.next_request()
|
||||
}
|
||||
|
||||
pub(crate) fn set_from_cold(
|
||||
@@ -124,14 +130,14 @@ impl SlidingSyncList {
|
||||
maximum_number_of_rooms: Option<u32>,
|
||||
rooms_list: Vector<RoomListEntry>,
|
||||
) {
|
||||
Observable::set(&mut self.state.write().unwrap(), SlidingSyncState::Preloaded);
|
||||
self.is_cold.store(true, Ordering::SeqCst);
|
||||
Observable::set(&mut self.inner.state.write().unwrap(), SlidingSyncState::Preloaded);
|
||||
self.inner.is_cold.store(true, Ordering::SeqCst);
|
||||
Observable::set(
|
||||
&mut self.maximum_number_of_rooms.write().unwrap(),
|
||||
&mut self.inner.maximum_number_of_rooms.write().unwrap(),
|
||||
maximum_number_of_rooms,
|
||||
);
|
||||
|
||||
let mut lock = self.rooms_list.write().unwrap();
|
||||
let mut lock = self.inner.rooms_list.write().unwrap();
|
||||
lock.clear();
|
||||
lock.append(rooms_list);
|
||||
}
|
||||
@@ -144,19 +150,19 @@ impl SlidingSyncList {
|
||||
/// Return a builder with the same settings as before
|
||||
pub fn new_builder(&self) -> SlidingSyncListBuilder {
|
||||
let mut builder = Self::builder()
|
||||
.name(&self.name)
|
||||
.sync_mode(self.sync_mode.clone())
|
||||
.sort(self.sort.clone())
|
||||
.required_state(self.required_state.clone())
|
||||
.full_sync_batch_size(self.full_sync_batch_size)
|
||||
.name(&self.inner.name)
|
||||
.sync_mode(self.inner.sync_mode.clone())
|
||||
.sort(self.inner.sort.clone())
|
||||
.required_state(self.inner.required_state.clone())
|
||||
.full_sync_batch_size(self.inner.full_sync_batch_size)
|
||||
.full_sync_maximum_number_of_rooms_to_fetch(
|
||||
self.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
self.inner.full_sync_maximum_number_of_rooms_to_fetch,
|
||||
)
|
||||
.send_updates_for_items(self.send_updates_for_items)
|
||||
.filters(self.filters.clone())
|
||||
.ranges(self.ranges.read().unwrap().clone());
|
||||
.send_updates_for_items(self.inner.send_updates_for_items)
|
||||
.filters(self.inner.filters.clone())
|
||||
.ranges(self.inner.ranges.read().unwrap().clone());
|
||||
|
||||
if let Some(timeline_limit) = Observable::get(&self.timeline_limit.read().unwrap()) {
|
||||
if let Some(timeline_limit) = Observable::get(&self.inner.timeline_limit.read().unwrap()) {
|
||||
builder = builder.timeline_limit(*timeline_limit);
|
||||
}
|
||||
|
||||
@@ -172,7 +178,7 @@ impl SlidingSyncList {
|
||||
U: Into<UInt>,
|
||||
{
|
||||
let ranges = ranges.into_iter().map(|(a, b)| (a.into(), b.into())).collect();
|
||||
Observable::set(&mut self.ranges.write().unwrap(), ranges);
|
||||
Observable::set(&mut self.inner.ranges.write().unwrap(), ranges);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -185,8 +191,7 @@ impl SlidingSyncList {
|
||||
where
|
||||
U: Into<UInt>,
|
||||
{
|
||||
let value = vec![(start.into(), end.into())];
|
||||
Observable::set(&mut self.ranges.write().unwrap(), value);
|
||||
self.inner.set_range(start, end);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -199,7 +204,7 @@ impl SlidingSyncList {
|
||||
where
|
||||
U: Into<UInt>,
|
||||
{
|
||||
Observable::update(&mut self.ranges.write().unwrap(), |ranges| {
|
||||
Observable::update(&mut self.inner.ranges.write().unwrap(), |ranges| {
|
||||
ranges.push((start.into(), end.into()));
|
||||
});
|
||||
|
||||
@@ -216,19 +221,19 @@ impl SlidingSyncList {
|
||||
/// Remember to cancel the existing stream and fetch a new one as this will
|
||||
/// only be applied on the next request.
|
||||
pub fn reset_ranges(&self) -> &Self {
|
||||
Observable::set(&mut self.ranges.write().unwrap(), Vec::new());
|
||||
Observable::set(&mut self.inner.ranges.write().unwrap(), Vec::new());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the current state.
|
||||
pub fn state(&self) -> SlidingSyncState {
|
||||
self.state.read().unwrap().clone()
|
||||
self.inner.state.read().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Get a stream of state.
|
||||
pub fn state_stream(&self) -> impl Stream<Item = SlidingSyncState> {
|
||||
Observable::subscribe(&self.state.read().unwrap())
|
||||
Observable::subscribe(&self.inner.state.read().unwrap())
|
||||
}
|
||||
|
||||
/// Get the current rooms list.
|
||||
@@ -236,23 +241,23 @@ impl SlidingSyncList {
|
||||
where
|
||||
R: for<'a> From<&'a RoomListEntry>,
|
||||
{
|
||||
self.rooms_list.read().unwrap().iter().map(|e| R::from(e)).collect()
|
||||
self.inner.rooms_list.read().unwrap().iter().map(|e| R::from(e)).collect()
|
||||
}
|
||||
|
||||
/// Get a stream of rooms list.
|
||||
pub fn rooms_list_stream(&self) -> impl Stream<Item = VectorDiff<RoomListEntry>> {
|
||||
ObservableVector::subscribe(&self.rooms_list.read().unwrap())
|
||||
ObservableVector::subscribe(&self.inner.rooms_list.read().unwrap())
|
||||
}
|
||||
|
||||
/// Get the maximum number of rooms. See [`Self::maximum_number_of_rooms`]
|
||||
/// to learn more.
|
||||
pub fn maximum_number_of_rooms(&self) -> Option<u32> {
|
||||
**self.maximum_number_of_rooms.read().unwrap()
|
||||
**self.inner.maximum_number_of_rooms.read().unwrap()
|
||||
}
|
||||
|
||||
/// Get a stream of rooms count.
|
||||
pub fn maximum_number_of_rooms_stream(&self) -> impl Stream<Item = Option<u32>> {
|
||||
Observable::subscribe(&self.maximum_number_of_rooms.read().unwrap())
|
||||
Observable::subscribe(&self.inner.maximum_number_of_rooms.read().unwrap())
|
||||
}
|
||||
|
||||
/// Find the current valid position of the room in the list `room_list`.
|
||||
@@ -261,30 +266,7 @@ impl SlidingSyncList {
|
||||
/// Invalid items are ignore. Return the total position the item was
|
||||
/// found in the room_list, return None otherwise.
|
||||
pub fn find_room_in_list(&self, room_id: &RoomId) -> Option<usize> {
|
||||
let ranges = self.ranges.read().unwrap();
|
||||
let listing = self.rooms_list.read().unwrap();
|
||||
|
||||
for (start_uint, end_uint) in ranges.iter() {
|
||||
let mut current_position: usize = (*start_uint).try_into().unwrap();
|
||||
let end: usize = (*end_uint).try_into().unwrap();
|
||||
let room_list_entries = listing.iter().skip(current_position);
|
||||
|
||||
for room_list_entry in room_list_entries {
|
||||
if let RoomListEntry::Filled(this_room_id) = room_list_entry {
|
||||
if room_id == this_room_id {
|
||||
return Some(current_position);
|
||||
}
|
||||
}
|
||||
|
||||
if current_position == end {
|
||||
break;
|
||||
}
|
||||
|
||||
current_position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.inner.find_room_in_list(room_id)
|
||||
}
|
||||
|
||||
/// Find the current valid position of the rooms in the lists `room_list`.
|
||||
@@ -294,55 +276,136 @@ impl SlidingSyncList {
|
||||
/// found in the `room_list`, will skip any room not found in the
|
||||
/// `rooms_list`.
|
||||
pub fn find_rooms_in_list(&self, room_ids: &[OwnedRoomId]) -> Vec<(usize, OwnedRoomId)> {
|
||||
let ranges = self.ranges.read().unwrap();
|
||||
let listing = self.rooms_list.read().unwrap();
|
||||
let mut rooms_found = Vec::new();
|
||||
|
||||
for (start_uint, end_uint) in ranges.iter() {
|
||||
let mut current_position: usize = (*start_uint).try_into().unwrap();
|
||||
let end: usize = (*end_uint).try_into().unwrap();
|
||||
let room_list_entries = listing.iter().skip(current_position);
|
||||
|
||||
for room_list_entry in room_list_entries {
|
||||
if let RoomListEntry::Filled(room_id) = room_list_entry {
|
||||
if room_ids.contains(room_id) {
|
||||
rooms_found.push((current_position, room_id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if current_position == end {
|
||||
break;
|
||||
}
|
||||
|
||||
current_position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
rooms_found
|
||||
self.inner.find_rooms_in_list(room_ids)
|
||||
}
|
||||
|
||||
/// Return the `room_id` at the given index.
|
||||
pub fn get_room_id(&self, index: usize) -> Option<OwnedRoomId> {
|
||||
self.rooms_list
|
||||
self.inner
|
||||
.rooms_list
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(index)
|
||||
.and_then(|room_list_entry| room_list_entry.as_room_id().map(ToOwned::to_owned))
|
||||
}
|
||||
|
||||
/// Update this `SlidingSyncList` from a response received and handled by
|
||||
/// its [`SlidingSyncListRequestGenerator`]
|
||||
///
|
||||
/// This method must not be called directly, except for testing purposes
|
||||
/// etc.
|
||||
///
|
||||
/// This method partially handles the response, and doesn't manage the
|
||||
/// entire state of [`SlidingSyncList`]. For example, the
|
||||
/// [`SlidingSyncList::state`] value is updated by
|
||||
/// [`SlidingSyncListRequestGenerator::handle_response`], not by this
|
||||
/// method.
|
||||
#[instrument(skip(self, ops), fields(name = self.name, ops_count = ops.len()))]
|
||||
fn handle_response(
|
||||
// Handle the response from the server.
|
||||
#[instrument(skip(self, ops), fields(name = self.name(), ops_count = ops.len()))]
|
||||
pub(super) fn handle_response(
|
||||
&mut self,
|
||||
maximum_number_of_rooms: u32,
|
||||
ops: &Vec<v4::SyncOp>,
|
||||
updated_rooms: &Vec<OwnedRoomId>,
|
||||
) -> Result<bool, Error> {
|
||||
let response = self.inner.update_state(
|
||||
maximum_number_of_rooms,
|
||||
ops,
|
||||
&self.inner.request_generator.read().unwrap().ranges,
|
||||
updated_rooms,
|
||||
)?;
|
||||
self.inner.update_request_generator_state(maximum_number_of_rooms);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncListInner {
|
||||
fn set_range<U>(&self, start: U, end: U)
|
||||
where
|
||||
U: Into<UInt>,
|
||||
{
|
||||
let value = vec![(start.into(), end.into())];
|
||||
Observable::set(&mut self.ranges.write().unwrap(), value);
|
||||
}
|
||||
|
||||
fn next_request(&self) -> Option<v4::SyncRequestList> {
|
||||
{
|
||||
// Use a dedicated scope to ensure the lock is released before continuing.
|
||||
let mut request_generator = self.request_generator.write().unwrap();
|
||||
|
||||
match request_generator.kind {
|
||||
// Cases where all rooms have been fully loaded.
|
||||
SlidingSyncListRequestGeneratorKind::PagingFullSync {
|
||||
fully_loaded: true, ..
|
||||
}
|
||||
| SlidingSyncListRequestGeneratorKind::GrowingFullSync {
|
||||
fully_loaded: true, ..
|
||||
}
|
||||
| SlidingSyncListRequestGeneratorKind::Selective => {
|
||||
// Let's copy all the ranges from `SlidingSyncList`.
|
||||
request_generator.ranges = self.ranges.read().unwrap().clone();
|
||||
}
|
||||
|
||||
SlidingSyncListRequestGeneratorKind::PagingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// In paging-mode, range starts at the number of fetched rooms. Since ranges are
|
||||
// inclusive, and since the number of fetched rooms starts at 1,
|
||||
// not at 0, there is no need to add 1 here.
|
||||
let range_start = number_of_fetched_rooms;
|
||||
let range_desired_size = batch_size;
|
||||
|
||||
// Create a new range, and use it as the current set of ranges.
|
||||
request_generator.ranges = vec![create_range(
|
||||
range_start,
|
||||
range_desired_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
self.maximum_number_of_rooms.read().unwrap().clone(),
|
||||
)?];
|
||||
}
|
||||
|
||||
SlidingSyncListRequestGeneratorKind::GrowingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// In growing-mode, range always starts from 0. However, the end is growing by
|
||||
// adding `batch_size` to the previous number of fetched rooms.
|
||||
let range_start = 0;
|
||||
let range_desired_size = number_of_fetched_rooms.saturating_add(batch_size);
|
||||
|
||||
// Create a new range, and use it as the current set of ranges.
|
||||
request_generator.ranges = vec![create_range(
|
||||
range_start,
|
||||
range_desired_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
self.maximum_number_of_rooms.read().unwrap().clone(),
|
||||
)?];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Here we go.
|
||||
Some(self.request())
|
||||
}
|
||||
|
||||
/// Build a [`SyncRequestList`][v4::SyncRequestList] based on the current
|
||||
/// state of the request generator.
|
||||
#[instrument(skip(self), fields(name = self.name, ranges = ?&self.ranges))]
|
||||
fn request(&self) -> v4::SyncRequestList {
|
||||
let ranges = self.request_generator.read().unwrap().ranges.clone();
|
||||
let sort = self.sort.clone();
|
||||
let required_state = self.required_state.clone();
|
||||
let timeline_limit = **self.timeline_limit.read().unwrap();
|
||||
let filters = self.filters.clone();
|
||||
|
||||
assign!(v4::SyncRequestList::default(), {
|
||||
ranges,
|
||||
room_details: assign!(v4::RoomDetailsConfig::default(), {
|
||||
required_state,
|
||||
timeline_limit,
|
||||
}),
|
||||
sort,
|
||||
filters,
|
||||
})
|
||||
}
|
||||
|
||||
// Update the [`SlidingSyncListInner`]'s state.
|
||||
fn update_state(
|
||||
&self,
|
||||
maximum_number_of_rooms: u32,
|
||||
ops: &Vec<v4::SyncOp>,
|
||||
@@ -446,6 +509,146 @@ impl SlidingSyncList {
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
/// Update the state of the [`SlidingSyncListRequestGenerator`].
|
||||
fn update_request_generator_state(&self, maximum_number_of_rooms: u32) {
|
||||
let mut request_generator = self.request_generator.write().unwrap();
|
||||
|
||||
let Some(range_end) = request_generator.ranges.first().map(|(_start, end)| u32::try_from(*end).unwrap()) else {
|
||||
error!(name = self.name, "The request generator must have a range.");
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
match &mut request_generator.kind {
|
||||
SlidingSyncListRequestGeneratorKind::PagingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
fully_loaded,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
}
|
||||
| SlidingSyncListRequestGeneratorKind::GrowingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
fully_loaded,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// Calculate the maximum bound for the range.
|
||||
// At this step, the server has given us a maximum number of rooms for this
|
||||
// list. That's our `range_maximum`.
|
||||
let mut range_maximum = maximum_number_of_rooms;
|
||||
|
||||
// But maybe the user has defined a maximum number of rooms to fetch? In this
|
||||
// case, let's take the minimum of the two.
|
||||
if let Some(maximum_number_of_rooms_to_fetch) = maximum_number_of_rooms_to_fetch {
|
||||
range_maximum = min(range_maximum, *maximum_number_of_rooms_to_fetch);
|
||||
}
|
||||
|
||||
// Finally, ranges are inclusive!
|
||||
range_maximum = range_maximum.saturating_sub(1);
|
||||
|
||||
// Now, we know what the maximum bound for the range is.
|
||||
|
||||
// The current range hasn't reached its maximum, let's continue.
|
||||
if range_end < range_maximum {
|
||||
// Update the number of fetched rooms forward. Do not forget that ranges are
|
||||
// inclusive, so let's add 1.
|
||||
*number_of_fetched_rooms = range_end.saturating_add(1);
|
||||
|
||||
// The list is still not fully loaded.
|
||||
*fully_loaded = false;
|
||||
|
||||
// Update the _list range_ to cover from 0 to `range_end`.
|
||||
// The list range is different from the request generator (this) range.
|
||||
self.set_range(0, range_end);
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::PartiallyLoaded;
|
||||
});
|
||||
}
|
||||
// Otherwise the current range has reached its maximum, we switched to `FullyLoaded`
|
||||
// mode.
|
||||
else {
|
||||
// The number of fetched rooms is set to the maximum too.
|
||||
*number_of_fetched_rooms = range_maximum;
|
||||
|
||||
// We update the `fully_loaded` marker.
|
||||
*fully_loaded = true;
|
||||
|
||||
// The range is covering the entire list, from 0 to its maximum.
|
||||
self.set_range(0, range_maximum);
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SlidingSyncListRequestGeneratorKind::Selective => {
|
||||
// Selective mode always loads everything.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_room_in_list(&self, room_id: &RoomId) -> Option<usize> {
|
||||
let ranges = self.ranges.read().unwrap();
|
||||
let listing = self.rooms_list.read().unwrap();
|
||||
|
||||
for (start_uint, end_uint) in ranges.iter() {
|
||||
let mut current_position: usize = (*start_uint).try_into().unwrap();
|
||||
let end: usize = (*end_uint).try_into().unwrap();
|
||||
let room_list_entries = listing.iter().skip(current_position);
|
||||
|
||||
for room_list_entry in room_list_entries {
|
||||
if let RoomListEntry::Filled(this_room_id) = room_list_entry {
|
||||
if room_id == this_room_id {
|
||||
return Some(current_position);
|
||||
}
|
||||
}
|
||||
|
||||
if current_position == end {
|
||||
break;
|
||||
}
|
||||
|
||||
current_position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn find_rooms_in_list(&self, room_ids: &[OwnedRoomId]) -> Vec<(usize, OwnedRoomId)> {
|
||||
let ranges = self.ranges.read().unwrap();
|
||||
let listing = self.rooms_list.read().unwrap();
|
||||
let mut rooms_found = Vec::new();
|
||||
|
||||
for (start_uint, end_uint) in ranges.iter() {
|
||||
let mut current_position: usize = (*start_uint).try_into().unwrap();
|
||||
let end: usize = (*end_uint).try_into().unwrap();
|
||||
let room_list_entries = listing.iter().skip(current_position);
|
||||
|
||||
for room_list_entry in room_list_entries {
|
||||
if let RoomListEntry::Filled(room_id) = room_list_entry {
|
||||
if room_ids.contains(room_id) {
|
||||
rooms_found.push((current_position, room_id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if current_position == end {
|
||||
break;
|
||||
}
|
||||
|
||||
current_position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
rooms_found
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -466,7 +669,7 @@ impl FrozenSlidingSyncList {
|
||||
let mut rooms = BTreeMap::new();
|
||||
let mut rooms_list = Vector::new();
|
||||
|
||||
for room_list_entry in source_list.rooms_list.read().unwrap().iter() {
|
||||
for room_list_entry in source_list.inner.rooms_list.read().unwrap().iter() {
|
||||
match room_list_entry {
|
||||
RoomListEntry::Filled(room_id) | RoomListEntry::Invalidated(room_id) => {
|
||||
rooms.insert(
|
||||
@@ -482,7 +685,7 @@ impl FrozenSlidingSyncList {
|
||||
}
|
||||
|
||||
FrozenSlidingSyncList {
|
||||
maximum_number_of_rooms: **source_list.maximum_number_of_rooms.read().unwrap(),
|
||||
maximum_number_of_rooms: source_list.maximum_number_of_rooms(),
|
||||
rooms_list,
|
||||
rooms,
|
||||
}
|
||||
@@ -775,14 +978,14 @@ mod tests {
|
||||
($left:ident == $right:ident on fields { $( $field:ident $( with $accessor:expr )? ),+ $(,)* } ) => {
|
||||
$(
|
||||
let left = {
|
||||
let $field = $left . $field;
|
||||
let $field = & $left . $field;
|
||||
|
||||
$( let $field = $accessor ; )?
|
||||
|
||||
$field
|
||||
};
|
||||
let right = {
|
||||
let $field = $right . $field;
|
||||
let $field = & $right . $field;
|
||||
|
||||
$( let $field = $accessor ; )?
|
||||
|
||||
@@ -822,28 +1025,33 @@ mod tests {
|
||||
#[test]
|
||||
fn test_sliding_sync_list_new_builder() {
|
||||
let list = SlidingSyncList {
|
||||
sync_mode: SlidingSyncMode::GrowingFullSync,
|
||||
sort: vec!["foo".to_string(), "bar".to_string()],
|
||||
required_state: vec![(StateEventType::RoomName, "baz".to_owned())],
|
||||
full_sync_batch_size: 42,
|
||||
full_sync_maximum_number_of_rooms_to_fetch: Some(153),
|
||||
send_updates_for_items: true,
|
||||
filters: Some(assign!(v4::SyncRequestListFilters::default(), {
|
||||
is_dm: Some(true),
|
||||
})),
|
||||
timeline_limit: Arc::new(StdRwLock::new(Observable::new(Some(uint!(7))))),
|
||||
name: "qux".to_string(),
|
||||
state: Arc::new(StdRwLock::new(Observable::new(SlidingSyncState::FullyLoaded))),
|
||||
maximum_number_of_rooms: Arc::new(StdRwLock::new(Observable::new(Some(11)))),
|
||||
rooms_list: Arc::new(StdRwLock::new(ObservableVector::from(vector![
|
||||
RoomListEntry::Empty
|
||||
]))),
|
||||
ranges: Arc::new(StdRwLock::new(Observable::new(vec![(uint!(0), uint!(9))]))),
|
||||
rooms_updated_broadcast: Arc::new(StdRwLock::new(Observable::new(()))),
|
||||
is_cold: Arc::new(AtomicBool::new(true)),
|
||||
inner: Arc::new(SlidingSyncListInner {
|
||||
sync_mode: SlidingSyncMode::GrowingFullSync,
|
||||
sort: vec!["foo".to_string(), "bar".to_string()],
|
||||
required_state: vec![(StateEventType::RoomName, "baz".to_owned())],
|
||||
full_sync_batch_size: 42,
|
||||
full_sync_maximum_number_of_rooms_to_fetch: Some(153),
|
||||
send_updates_for_items: true,
|
||||
filters: Some(assign!(v4::SyncRequestListFilters::default(), {
|
||||
is_dm: Some(true),
|
||||
})),
|
||||
timeline_limit: StdRwLock::new(Observable::new(Some(uint!(7)))),
|
||||
name: "qux".to_string(),
|
||||
state: StdRwLock::new(Observable::new(SlidingSyncState::FullyLoaded)),
|
||||
maximum_number_of_rooms: StdRwLock::new(Observable::new(Some(11))),
|
||||
rooms_list: StdRwLock::new(ObservableVector::from(vector![RoomListEntry::Empty])),
|
||||
ranges: StdRwLock::new(Observable::new(vec![(uint!(0), uint!(9))])),
|
||||
rooms_updated_broadcast: StdRwLock::new(Observable::new(())),
|
||||
is_cold: AtomicBool::new(true),
|
||||
request_generator: StdRwLock::new(
|
||||
SlidingSyncListRequestGenerator::new_growing_full_sync(42, Some(153)),
|
||||
),
|
||||
}),
|
||||
};
|
||||
|
||||
let new_list = list.new_builder().build().unwrap();
|
||||
let list = list.inner;
|
||||
let new_list = new_list.inner;
|
||||
|
||||
assert_fields_eq!(
|
||||
list == new_list on fields {
|
||||
@@ -853,7 +1061,7 @@ mod tests {
|
||||
full_sync_batch_size,
|
||||
full_sync_maximum_number_of_rooms_to_fetch,
|
||||
send_updates_for_items,
|
||||
filters with filters.unwrap().is_dm,
|
||||
filters with filters.as_ref().unwrap().is_dm,
|
||||
timeline_limit with **timeline_limit.read().unwrap(),
|
||||
name,
|
||||
ranges with ranges.read().unwrap().clone(),
|
||||
@@ -876,7 +1084,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(0, 1), (2, 3)]);
|
||||
@@ -885,7 +1093,7 @@ mod tests {
|
||||
list.set_ranges(ranges![(4, 5), (6, 7)]);
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(4, 5), (6, 7)]);
|
||||
@@ -902,7 +1110,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(0, 1), (2, 3)]);
|
||||
@@ -911,7 +1119,7 @@ mod tests {
|
||||
list.set_range(4u32, 5);
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(4, 5)]);
|
||||
@@ -928,7 +1136,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(0, 1)]);
|
||||
@@ -937,7 +1145,7 @@ mod tests {
|
||||
list.add_range(2u32, 3);
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(0, 1), (2, 3)]);
|
||||
@@ -954,7 +1162,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert_eq!(ranges, &ranges![(0, 1)]);
|
||||
@@ -963,13 +1171,14 @@ mod tests {
|
||||
list.reset_ranges();
|
||||
|
||||
{
|
||||
let lock = list.ranges.read().unwrap();
|
||||
let lock = list.inner.ranges.read().unwrap();
|
||||
let ranges = Observable::get(&lock);
|
||||
|
||||
assert!(ranges.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn check_find_room_in_list() -> Result<()> {
|
||||
let list = SlidingSyncList::builder().name("foo").add_range(0u32, 9u32).build().unwrap();
|
||||
@@ -1022,7 +1231,7 @@ mod tests {
|
||||
list.handle_response(
|
||||
10u32,
|
||||
&vec![update],
|
||||
&vec![(uint!(0), uint!(3)), (uint!(8), uint!(9))],
|
||||
// &vec![(uint!(0), uint!(3)), (uint!(8), uint!(9))],
|
||||
&vec![],
|
||||
)
|
||||
.unwrap();
|
||||
@@ -1038,6 +1247,7 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn test_room_list_entry_is_empty_or_invalidated() {
|
||||
|
||||
@@ -31,246 +31,101 @@
|
||||
|
||||
use std::cmp::min;
|
||||
|
||||
use eyeball::unique::Observable;
|
||||
use ruma::{api::client::sync::sync_events::v4, assign, OwnedRoomId, UInt};
|
||||
use tracing::{error, instrument};
|
||||
|
||||
use super::{Error, SlidingSyncList, SlidingSyncState};
|
||||
use ruma::UInt;
|
||||
|
||||
/// The kind of request generator.
|
||||
#[derive(Debug)]
|
||||
enum GeneratorKind {
|
||||
// Growing-mode (see [`SlidingSyncMode`]).
|
||||
pub(super) enum SlidingSyncListRequestGeneratorKind {
|
||||
/// Growing-mode (see [`SlidingSyncMode`]).
|
||||
GrowingFullSync {
|
||||
// Number of fetched rooms.
|
||||
number_of_fetched_rooms: u32,
|
||||
// Size of the batch, used to grow the range to fetch more rooms.
|
||||
/// Size of the batch, used to grow the range to fetch more rooms.
|
||||
batch_size: u32,
|
||||
// Maximum number of rooms to fetch (see
|
||||
// [`SlidingSyncList::full_sync_maximum_number_of_rooms_to_fetch`]).
|
||||
/// Maximum number of rooms to fetch (see
|
||||
/// [`SlidingSyncList::full_sync_maximum_number_of_rooms_to_fetch`]).
|
||||
maximum_number_of_rooms_to_fetch: Option<u32>,
|
||||
// Whether all rooms have been loaded.
|
||||
/// Number of rooms that have been already fetched.
|
||||
number_of_fetched_rooms: u32,
|
||||
/// Whether all rooms have been loaded.
|
||||
fully_loaded: bool,
|
||||
},
|
||||
|
||||
// Paging-mode (see [`SlidingSyncMode`]).
|
||||
/// Paging-mode (see [`SlidingSyncMode`]).
|
||||
PagingFullSync {
|
||||
// Number of fetched rooms.
|
||||
number_of_fetched_rooms: u32,
|
||||
// Size of the batch, used to grow the range to fetch more rooms.
|
||||
/// Size of the batch, used to grow the range to fetch more rooms.
|
||||
batch_size: u32,
|
||||
// Maximum number of rooms to fetch (see
|
||||
// [`SlidingSyncList::full_sync_maximum_number_of_rooms_to_fetch`]).
|
||||
/// Maximum number of rooms to fetch (see
|
||||
/// [`SlidingSyncList::full_sync_maximum_number_of_rooms_to_fetch`]).
|
||||
maximum_number_of_rooms_to_fetch: Option<u32>,
|
||||
// Whether all romms have been loaded.
|
||||
/// Number of rooms that have been already fetched.
|
||||
number_of_fetched_rooms: u32,
|
||||
/// Whether all romms have been loaded.
|
||||
fully_loaded: bool,
|
||||
},
|
||||
|
||||
// Selective-mode (see [`SlidingSyncMode`]).
|
||||
/// Selective-mode (see [`SlidingSyncMode`]).
|
||||
Selective,
|
||||
}
|
||||
|
||||
/// A request generator for [`SlidingSyncList`].
|
||||
#[derive(Debug)]
|
||||
pub(in super::super) struct SlidingSyncListRequestGenerator {
|
||||
/// The parent [`SlidingSyncList`] object that has created this request
|
||||
/// generator.
|
||||
list: SlidingSyncList,
|
||||
/// The current range used by this request generator.
|
||||
ranges: Vec<(UInt, UInt)>,
|
||||
pub(super) ranges: Vec<(UInt, UInt)>,
|
||||
/// The kind of request generator.
|
||||
kind: GeneratorKind,
|
||||
pub(super) kind: SlidingSyncListRequestGeneratorKind,
|
||||
}
|
||||
|
||||
impl SlidingSyncListRequestGenerator {
|
||||
/// Create a new request generator configured for paging-mode.
|
||||
pub(super) fn new_with_paging_full_sync(list: SlidingSyncList) -> Self {
|
||||
let batch_size = list.full_sync_batch_size;
|
||||
let maximum_number_of_rooms_to_fetch = list.full_sync_maximum_number_of_rooms_to_fetch;
|
||||
// If a range exists, let's consider it's been used to load existing room. So
|
||||
// let's start from the end of the range. It can be useful when we resume a sync
|
||||
// for example. Otherwise let's use the default value.
|
||||
let number_of_fetched_rooms = list
|
||||
.ranges
|
||||
.read()
|
||||
.unwrap()
|
||||
.first()
|
||||
.map(|(_start, end)| u32::try_from(*end).unwrap().saturating_add(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
pub(super) fn new_paging_full_sync(
|
||||
batch_size: u32,
|
||||
maximum_number_of_rooms_to_fetch: Option<u32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
list,
|
||||
ranges: Vec::new(),
|
||||
kind: GeneratorKind::PagingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
kind: SlidingSyncListRequestGeneratorKind::PagingFullSync {
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
number_of_fetched_rooms: 0,
|
||||
fully_loaded: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new request generator configured for growing-mode.
|
||||
pub(super) fn new_with_growing_full_sync(list: SlidingSyncList) -> Self {
|
||||
let batch_size = list.full_sync_batch_size;
|
||||
let maximum_number_of_rooms_to_fetch = list.full_sync_maximum_number_of_rooms_to_fetch;
|
||||
// If a range exists, let's consider it's been used to load existing room. So
|
||||
// let's start from the end of the range. It can be useful when we resume a sync
|
||||
// for example. Otherwise let's use the default value.
|
||||
let number_of_fetched_rooms = list
|
||||
.ranges
|
||||
.read()
|
||||
.unwrap()
|
||||
.first()
|
||||
.map(|(_start, end)| u32::try_from(*end).unwrap().saturating_add(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
pub(super) fn new_growing_full_sync(
|
||||
batch_size: u32,
|
||||
maximum_number_of_rooms_to_fetch: Option<u32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
list,
|
||||
ranges: Vec::new(),
|
||||
kind: GeneratorKind::GrowingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
kind: SlidingSyncListRequestGeneratorKind::GrowingFullSync {
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
number_of_fetched_rooms: 0,
|
||||
fully_loaded: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new request generator configured for selective-mode.
|
||||
pub(super) fn new_selective(list: SlidingSyncList) -> Self {
|
||||
Self { list, ranges: Vec::new(), kind: GeneratorKind::Selective }
|
||||
}
|
||||
|
||||
/// Build a [`SyncRequestList`][v4::SyncRequestList].
|
||||
#[instrument(skip(self), fields(name = self.list.name, ranges = ?&self.ranges))]
|
||||
fn build_request(&self) -> v4::SyncRequestList {
|
||||
let sort = self.list.sort.clone();
|
||||
let required_state = self.list.required_state.clone();
|
||||
let timeline_limit = **self.list.timeline_limit.read().unwrap();
|
||||
let filters = self.list.filters.clone();
|
||||
|
||||
assign!(v4::SyncRequestList::default(), {
|
||||
ranges: self.ranges.clone(),
|
||||
room_details: assign!(v4::RoomDetailsConfig::default(), {
|
||||
required_state,
|
||||
timeline_limit,
|
||||
}),
|
||||
sort,
|
||||
filters,
|
||||
})
|
||||
}
|
||||
|
||||
// Handle the response from the server.
|
||||
#[instrument(skip_all, fields(name = self.list.name, rooms_count, has_ops = !ops.is_empty()))]
|
||||
pub(in super::super) fn handle_response(
|
||||
&mut self,
|
||||
maximum_number_of_rooms: u32,
|
||||
ops: &Vec<v4::SyncOp>,
|
||||
updated_rooms: &Vec<OwnedRoomId>,
|
||||
) -> Result<bool, Error> {
|
||||
let response =
|
||||
self.list.handle_response(maximum_number_of_rooms, ops, &self.ranges, updated_rooms)?;
|
||||
|
||||
self.update_state(maximum_number_of_rooms);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Update the state of the generator.
|
||||
fn update_state(&mut self, maximum_number_of_rooms: u32) {
|
||||
let Some(range_end) = self.ranges.first().map(|(_start, end)| u32::try_from(*end).unwrap()) else {
|
||||
error!(name = self.list.name, "The request generator must have a range.");
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
match &mut self.kind {
|
||||
GeneratorKind::PagingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
fully_loaded,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
}
|
||||
| GeneratorKind::GrowingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
fully_loaded,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// Calculate the maximum bound for the range.
|
||||
// At this step, the server has given us a maximum number of rooms for this
|
||||
// list. That's our `range_maximum`.
|
||||
let mut range_maximum = maximum_number_of_rooms;
|
||||
|
||||
// But maybe the user has defined a maximum number of rooms to fetch? In this
|
||||
// case, let's take the minimum of the two.
|
||||
if let Some(maximum_number_of_rooms_to_fetch) = maximum_number_of_rooms_to_fetch {
|
||||
range_maximum = min(range_maximum, *maximum_number_of_rooms_to_fetch);
|
||||
}
|
||||
|
||||
// Finally, ranges are inclusive!
|
||||
range_maximum = range_maximum.saturating_sub(1);
|
||||
|
||||
// Now, we know what the maximum bound for the range is.
|
||||
|
||||
// The current range hasn't reached its maximum, let's continue.
|
||||
if range_end < range_maximum {
|
||||
// Update the _list range_ to cover from 0 to `range_end`.
|
||||
// The list range is different from the request generator (this) range.
|
||||
self.list.set_range(0, range_end);
|
||||
|
||||
// Update the number of fetched rooms forward. Do not forget that ranges are
|
||||
// inclusive, so let's add 1.
|
||||
*number_of_fetched_rooms = range_end.saturating_add(1);
|
||||
|
||||
// The list is still not fully loaded.
|
||||
*fully_loaded = false;
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.list.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::PartiallyLoaded;
|
||||
});
|
||||
}
|
||||
// Otherwise the current range has reached its maximum, we switched to `FullyLoaded`
|
||||
// mode.
|
||||
else {
|
||||
// The range is covering the entire list, from 0 to its maximum.
|
||||
self.list.set_range(0, range_maximum);
|
||||
|
||||
// The number of fetched rooms is set to the maximum too.
|
||||
*number_of_fetched_rooms = range_maximum;
|
||||
|
||||
// And we update the `fully_loaded` marker.
|
||||
*fully_loaded = true;
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.list.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
GeneratorKind::Selective => {
|
||||
// Selective mode always loads everything.
|
||||
Observable::update_eq(&mut self.list.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
}
|
||||
}
|
||||
pub(super) fn new_selective() -> Self {
|
||||
Self { ranges: Vec::new(), kind: SlidingSyncListRequestGeneratorKind::Selective }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_fully_loaded(&self) -> bool {
|
||||
match self.kind {
|
||||
GeneratorKind::PagingFullSync { fully_loaded, .. }
|
||||
| GeneratorKind::GrowingFullSync { fully_loaded, .. } => fully_loaded,
|
||||
GeneratorKind::Selective => true,
|
||||
SlidingSyncListRequestGeneratorKind::PagingFullSync { fully_loaded, .. }
|
||||
| SlidingSyncListRequestGeneratorKind::GrowingFullSync { fully_loaded, .. } => {
|
||||
fully_loaded
|
||||
}
|
||||
SlidingSyncListRequestGeneratorKind::Selective => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_range(
|
||||
pub(super) fn create_range(
|
||||
start: u32,
|
||||
desired_size: u32,
|
||||
maximum_number_of_rooms_to_fetch: Option<u32>,
|
||||
@@ -309,77 +164,14 @@ fn create_range(
|
||||
Some((start.into(), end.into()))
|
||||
}
|
||||
|
||||
impl Iterator for SlidingSyncListRequestGenerator {
|
||||
type Item = v4::SyncRequestList;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.kind {
|
||||
// Cases where all rooms have been fully loaded.
|
||||
GeneratorKind::PagingFullSync { fully_loaded: true, .. }
|
||||
| GeneratorKind::GrowingFullSync { fully_loaded: true, .. }
|
||||
| GeneratorKind::Selective => {
|
||||
// Let's copy all the ranges from the parent `SlidingSyncList`, and build a
|
||||
// request for them.
|
||||
self.ranges = self.list.ranges.read().unwrap().clone();
|
||||
|
||||
// Here we go.
|
||||
Some(self.build_request())
|
||||
}
|
||||
|
||||
GeneratorKind::PagingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// In paging-mode, range starts at the number of fetched rooms. Since ranges are
|
||||
// inclusive, and since the number of fetched rooms starts at 1,
|
||||
// not at 0, there is no need to add 1 here.
|
||||
let range_start = number_of_fetched_rooms;
|
||||
let range_desired_size = batch_size;
|
||||
|
||||
// Create a new range, and use it as the current set of ranges.
|
||||
self.ranges = vec![create_range(
|
||||
range_start,
|
||||
range_desired_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
self.list.maximum_number_of_rooms(),
|
||||
)?];
|
||||
|
||||
// Here we go.
|
||||
Some(self.build_request())
|
||||
}
|
||||
|
||||
GeneratorKind::GrowingFullSync {
|
||||
number_of_fetched_rooms,
|
||||
batch_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
..
|
||||
} => {
|
||||
// In growing-mode, range always starts from 0. However, the end is growing by
|
||||
// adding `batch_size` to the previous number of fetched rooms.
|
||||
let range_start = 0;
|
||||
let range_desired_size = number_of_fetched_rooms.saturating_add(batch_size);
|
||||
|
||||
self.ranges = vec![create_range(
|
||||
range_start,
|
||||
range_desired_size,
|
||||
maximum_number_of_rooms_to_fetch,
|
||||
self.list.maximum_number_of_rooms(),
|
||||
)?];
|
||||
|
||||
// Here we go.
|
||||
Some(self.build_request())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruma::uint;
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
super::{SlidingSyncList, SlidingSyncState},
|
||||
*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_create_range_from() {
|
||||
@@ -425,7 +217,6 @@ mod tests {
|
||||
macro_rules! assert_request_and_response {
|
||||
(
|
||||
list = $list:ident,
|
||||
generator = $generator:ident,
|
||||
maximum_number_of_rooms = $maximum_number_of_rooms:expr,
|
||||
$(
|
||||
next => {
|
||||
@@ -442,14 +233,14 @@ mod tests {
|
||||
$(
|
||||
{
|
||||
// Generate a new request.
|
||||
let request = $generator.next().unwrap();
|
||||
let request = $list.next_request().unwrap();
|
||||
|
||||
assert_eq!(request.ranges, [ $( (uint!( $range_start ), uint!( $range_end )) ),* ]);
|
||||
|
||||
// Fake a response.
|
||||
let _ = $generator.handle_response($maximum_number_of_rooms, &vec![], &vec![]);
|
||||
let _ = $list.handle_response($maximum_number_of_rooms, &vec![], &vec![]);
|
||||
|
||||
assert_eq!($generator.is_fully_loaded(), $is_fully_loaded);
|
||||
assert_eq!($list.inner.request_generator.read().unwrap().is_fully_loaded(), $is_fully_loaded);
|
||||
assert_eq!($list.state(), SlidingSyncState::$list_state);
|
||||
}
|
||||
)*
|
||||
@@ -458,17 +249,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generator_paging_full_sync() {
|
||||
let list = SlidingSyncList::builder()
|
||||
let mut list = SlidingSyncList::builder()
|
||||
.sync_mode(crate::SlidingSyncMode::PagingFullSync)
|
||||
.name("testing")
|
||||
.full_sync_batch_size(10)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut generator = list.request_generator();
|
||||
|
||||
assert_request_and_response! {
|
||||
list = list,
|
||||
generator = generator,
|
||||
maximum_number_of_rooms = 25,
|
||||
next => {
|
||||
ranges = [0; 9],
|
||||
@@ -502,18 +291,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
|
||||
let list = SlidingSyncList::builder()
|
||||
let mut list = SlidingSyncList::builder()
|
||||
.sync_mode(crate::SlidingSyncMode::PagingFullSync)
|
||||
.name("testing")
|
||||
.full_sync_batch_size(10)
|
||||
.full_sync_maximum_number_of_rooms_to_fetch(22)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut generator = list.request_generator();
|
||||
|
||||
assert_request_and_response! {
|
||||
list = list,
|
||||
generator = generator,
|
||||
maximum_number_of_rooms = 25,
|
||||
next => {
|
||||
ranges = [0; 9],
|
||||
@@ -547,17 +334,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generator_growing_full_sync() {
|
||||
let list = SlidingSyncList::builder()
|
||||
let mut list = SlidingSyncList::builder()
|
||||
.sync_mode(crate::SlidingSyncMode::GrowingFullSync)
|
||||
.name("testing")
|
||||
.full_sync_batch_size(10)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut generator = list.request_generator();
|
||||
|
||||
assert_request_and_response! {
|
||||
list = list,
|
||||
generator = generator,
|
||||
maximum_number_of_rooms = 25,
|
||||
next => {
|
||||
ranges = [0; 9],
|
||||
@@ -591,18 +376,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
|
||||
let list = SlidingSyncList::builder()
|
||||
let mut list = SlidingSyncList::builder()
|
||||
.sync_mode(crate::SlidingSyncMode::GrowingFullSync)
|
||||
.name("testing")
|
||||
.full_sync_batch_size(10)
|
||||
.full_sync_maximum_number_of_rooms_to_fetch(22)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut generator = list.request_generator();
|
||||
|
||||
assert_request_and_response! {
|
||||
list = list,
|
||||
generator = generator,
|
||||
maximum_number_of_rooms = 25,
|
||||
next => {
|
||||
ranges = [0; 9],
|
||||
@@ -636,17 +419,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_generator_selective() {
|
||||
let list = SlidingSyncList::builder()
|
||||
let mut list = SlidingSyncList::builder()
|
||||
.sync_mode(crate::SlidingSyncMode::Selective)
|
||||
.name("testing")
|
||||
.ranges(vec![(0u32, 10), (42, 153)])
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut generator = list.request_generator();
|
||||
|
||||
assert_request_and_response! {
|
||||
list = list,
|
||||
generator = generator,
|
||||
maximum_number_of_rooms = 25,
|
||||
// The maximum number of rooms is reached directly!
|
||||
next => {
|
||||
|
||||
@@ -849,7 +849,7 @@ impl SlidingSync {
|
||||
/// stream created after this. The old stream will still continue to use the
|
||||
/// previous set of lists.
|
||||
pub fn add_list(&self, list: SlidingSyncList) -> Option<SlidingSyncList> {
|
||||
self.inner.lists.write().unwrap().insert(list.name.clone(), list)
|
||||
self.inner.lists.write().unwrap().insert(list.name().to_owned(), list)
|
||||
}
|
||||
|
||||
/// Lookup a set of rooms
|
||||
@@ -910,12 +910,12 @@ impl SlidingSync {
|
||||
}
|
||||
|
||||
/// Handle the HTTP response.
|
||||
#[instrument(skip_all, fields(lists = list_generators.len()))]
|
||||
#[instrument(skip_all, fields(lists = lists.len()))]
|
||||
fn handle_response(
|
||||
&self,
|
||||
sliding_sync_response: v4::Response,
|
||||
mut sync_response: SyncResponse,
|
||||
list_generators: &mut BTreeMap<String, SlidingSyncListRequestGenerator>,
|
||||
lists: &mut BTreeMap<String, SlidingSyncList>,
|
||||
) -> Result<UpdateSummary, crate::Error> {
|
||||
{
|
||||
debug!(
|
||||
@@ -969,7 +969,7 @@ impl SlidingSync {
|
||||
let mut updated_lists = Vec::new();
|
||||
|
||||
for (name, updates) in sliding_sync_response.lists {
|
||||
let Some(list_generator) = list_generators.get_mut(&name) else {
|
||||
let Some(list) = lists.get_mut(&name) else {
|
||||
error!("Response for list `{name}` - unknown to us; skipping");
|
||||
|
||||
continue
|
||||
@@ -978,11 +978,7 @@ impl SlidingSync {
|
||||
let maximum_number_of_rooms: u32 =
|
||||
updates.count.try_into().expect("the list total count convertible into u32");
|
||||
|
||||
if list_generator.handle_response(
|
||||
maximum_number_of_rooms,
|
||||
&updates.ops,
|
||||
&updated_rooms,
|
||||
)? {
|
||||
if list.handle_response(maximum_number_of_rooms, &updates.ops, &updated_rooms)? {
|
||||
updated_lists.push(name.clone());
|
||||
}
|
||||
}
|
||||
@@ -1001,28 +997,28 @@ impl SlidingSync {
|
||||
async fn sync_once(
|
||||
&self,
|
||||
stream_id: &str,
|
||||
list_generators: Arc<Mutex<BTreeMap<String, SlidingSyncListRequestGenerator>>>,
|
||||
lists: Arc<Mutex<BTreeMap<String, SlidingSyncList>>>,
|
||||
) -> Result<Option<UpdateSummary>> {
|
||||
let mut lists = BTreeMap::new();
|
||||
let mut requests_lists = BTreeMap::new();
|
||||
|
||||
{
|
||||
let mut list_generators_lock = list_generators.lock().unwrap();
|
||||
let list_generators = list_generators_lock.borrow_mut();
|
||||
let mut lists_lock = lists.lock().unwrap();
|
||||
let lists = lists_lock.borrow_mut();
|
||||
let mut lists_to_remove = Vec::new();
|
||||
|
||||
for (name, generator) in list_generators.iter_mut() {
|
||||
if let Some(request) = generator.next() {
|
||||
lists.insert(name.clone(), request);
|
||||
for (name, list) in lists.iter_mut() {
|
||||
if let Some(list_request) = list.next_request() {
|
||||
requests_lists.insert(name.clone(), list_request);
|
||||
} else {
|
||||
lists_to_remove.push(name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for list_name in lists_to_remove {
|
||||
list_generators.remove(&list_name);
|
||||
lists.remove(&list_name);
|
||||
}
|
||||
|
||||
if list_generators.is_empty() {
|
||||
if lists.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
@@ -1053,7 +1049,7 @@ impl SlidingSync {
|
||||
// request. We use the (optional) `txn_id` field for that.
|
||||
txn_id: Some(stream_id.to_owned()),
|
||||
timeout: Some(timeout),
|
||||
lists,
|
||||
lists: requests_lists,
|
||||
room_subscriptions,
|
||||
unsubscribe_rooms,
|
||||
extensions,
|
||||
@@ -1140,7 +1136,7 @@ impl SlidingSync {
|
||||
|
||||
debug!(?sync_response, "Sliding Sync response has been handled by the client");
|
||||
|
||||
let updates = this.handle_response(response, sync_response, list_generators.lock().unwrap().borrow_mut())?;
|
||||
let updates = this.handle_response(response, sync_response, lists.lock().unwrap().borrow_mut())?;
|
||||
|
||||
this.cache_to_storage().await?;
|
||||
|
||||
@@ -1160,24 +1156,15 @@ impl SlidingSync {
|
||||
#[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
|
||||
#[instrument(name = "sync_stream", skip_all, parent = &self.inner.client.inner.root_span)]
|
||||
pub fn stream<'a>(&'a self) -> impl Stream<Item = Result<UpdateSummary, crate::Error>> + 'a {
|
||||
// Collect all the lists that need to be updated.
|
||||
let list_generators = {
|
||||
let mut list_generators = BTreeMap::new();
|
||||
let lock = self.inner.lists.read().unwrap();
|
||||
|
||||
for (name, lists) in lock.iter() {
|
||||
list_generators.insert(name.clone(), lists.request_generator());
|
||||
}
|
||||
|
||||
list_generators
|
||||
};
|
||||
// Copy all the lists.
|
||||
let lists = Arc::new(Mutex::new(self.inner.lists.read().unwrap().clone()));
|
||||
|
||||
// Define a stream ID.
|
||||
let stream_id = Uuid::new_v4().to_string();
|
||||
|
||||
debug!(?self.inner.extensions, stream_id, "About to run the sync stream");
|
||||
|
||||
let instrument_span = Span::current();
|
||||
let list_generators = Arc::new(Mutex::new(list_generators));
|
||||
|
||||
async_stream::stream! {
|
||||
loop {
|
||||
@@ -1187,7 +1174,7 @@ impl SlidingSync {
|
||||
debug!(?self.inner.extensions, "Sync stream loop is running");
|
||||
});
|
||||
|
||||
match self.sync_once(&stream_id, list_generators.clone()).instrument(sync_span.clone()).await {
|
||||
match self.sync_once(&stream_id, lists.clone()).instrument(sync_span.clone()).await {
|
||||
Ok(Some(updates)) => {
|
||||
self.inner.reset_counter.store(0, Ordering::SeqCst);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user