feat(sdk): Implement SlidingSyncList::set_sync_mode

feat(sdk): Implement `SlidingSyncList::set_sync_mode`
This commit is contained in:
Ivan Enderlin
2023-05-22 14:38:01 +02:00
committed by GitHub
3 changed files with 248 additions and 48 deletions

View File

@@ -202,7 +202,7 @@ impl SlidingSyncListBuilder {
let list = SlidingSyncList {
inner: Arc::new(SlidingSyncListInner {
// From the builder
sync_mode: self.sync_mode.clone(),
sync_mode: StdRwLock::new(self.sync_mode.clone()),
sort: self.sort,
required_state: self.required_state,
filters: self.filters,

View File

@@ -60,9 +60,32 @@ impl SlidingSyncList {
self.inner.name.as_str()
}
/// Change the sync-mode.
///
/// It is sometimes necessary to change the sync-mode of a list on-the-fly.
///
/// This will change the sync-mode but also the request generator. A new
/// request generator is generated. Since requests are calculated based on
/// the request generator, changing the sync-mode is equivalent to
/// “resetting” the list. It's actually not calling `Self::reset`, which
/// means that the state is not reset **purposely**. The ranges and the
/// state will be updated when the next request will be sent and a
/// response will be received. The maximum number of rooms won't change.
pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) -> Result<()> {
self.inner.set_sync_mode(sync_mode);
// When the sync mode is changed, the sync loop must skip over any work in its
// iteration and jump to the next iteration.
self.inner.internal_channel_blocking_send(
SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
)?;
Ok(())
}
/// Set the ranges to fetch.
pub fn set_ranges(&self, ranges: &[RangeInclusive<Bound>]) -> Result<(), Error> {
if self.inner.sync_mode.ranges_can_be_modified_by_user().not() {
if self.inner.sync_mode.read().unwrap().ranges_can_be_modified_by_user().not() {
return Err(Error::CannotModifyRanges(self.name().to_owned()));
}
@@ -74,7 +97,7 @@ impl SlidingSyncList {
/// Reset the ranges to a particular set.
pub fn set_range(&self, range: RangeInclusive<Bound>) -> Result<(), Error> {
if self.inner.sync_mode.ranges_can_be_modified_by_user().not() {
if self.inner.sync_mode.read().unwrap().ranges_can_be_modified_by_user().not() {
return Err(Error::CannotModifyRanges(self.name().to_owned()));
}
@@ -86,7 +109,7 @@ impl SlidingSyncList {
/// Set the ranges to fetch.
pub fn add_range(&self, range: RangeInclusive<Bound>) -> Result<(), Error> {
if self.inner.sync_mode.ranges_can_be_modified_by_user().not() {
if self.inner.sync_mode.read().unwrap().ranges_can_be_modified_by_user().not() {
return Err(Error::CannotModifyRanges(self.name().to_owned()));
}
@@ -103,7 +126,7 @@ impl SlidingSyncList {
/// order but you will only receive updates when for rooms entering or
/// leaving the set.
pub fn reset_ranges(&self) -> Result<(), Error> {
if self.inner.sync_mode.ranges_can_be_modified_by_user().not() {
if self.inner.sync_mode.read().unwrap().ranges_can_be_modified_by_user().not() {
return Err(Error::CannotModifyRanges(self.name().to_owned()));
}
@@ -186,6 +209,9 @@ impl SlidingSyncList {
}
/// Calculate the next request and return it.
///
/// The next request is entirely calculated based on the request generator
/// ([`SlidingSyncListRequestGenerator`]).
pub(super) fn next_request(&mut self) -> Result<v4::SyncRequestList, Error> {
self.inner.next_request()
}
@@ -229,11 +255,11 @@ impl SlidingSyncList {
pub(super) fn reset(&self) -> Result<(), Error> {
self.inner.reset();
// When a list is reset, the sync loop must be “restarted”.
self.inner
.sliding_sync_internal_channel_sender
.blocking_send(SlidingSyncInternalMessage::ContinueSyncLoop)
.map_err(|_| Error::InternalChannelIsBroken)?;
// When a list is reset, the sync loop must skip over any work in its iteration
// and jump to the next iteration.
self.inner.internal_channel_blocking_send(
SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
)?;
Ok(())
}
@@ -258,7 +284,7 @@ pub(super) struct SlidingSyncListInner {
state: StdRwLock<Observable<SlidingSyncState>>,
/// Which [`SlidingSyncMode`] to start this list under.
sync_mode: SlidingSyncMode,
sync_mode: StdRwLock<SlidingSyncMode>,
/// Sort the room list by this.
sort: Vec<String>,
@@ -300,6 +326,23 @@ pub(super) struct SlidingSyncListInner {
}
impl SlidingSyncListInner {
/// Change the sync-mode.
///
/// This will change the sync-mode but also the request generator.
///
/// [`Self::ranges`] and [`Self::state`] will be updated when the next
/// request will be sent and a response will be received. The
/// [`Self::maximum_number_of_rooms`] won't change.
pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) {
// Acquire both locks before modifying the values their hold.
let mut sync_mode_lock = self.sync_mode.write().unwrap();
let mut request_generator_lock = self.request_generator.write().unwrap();
// Now we can modify both values.
*sync_mode_lock = sync_mode.clone();
*request_generator_lock = SlidingSyncListRequestGenerator::new(sync_mode);
}
/// Reset and add new ranges.
fn set_ranges(&self, ranges: &[RangeInclusive<Bound>]) {
*self.ranges.write().unwrap() = ranges.to_vec();
@@ -616,6 +659,16 @@ impl SlidingSyncListInner {
Ok(())
}
/// Send a message over the internal channel.
fn internal_channel_blocking_send(
&self,
message: SlidingSyncInternalMessage,
) -> Result<(), Error> {
self.sliding_sync_internal_channel_sender
.blocking_send(message)
.map_err(|_| Error::InternalChannelIsBroken)
}
}
#[instrument(skip(operations))]
@@ -1140,7 +1193,7 @@ mod tests {
maximum_number_of_rooms = $maximum_number_of_rooms:expr,
$(
next => {
ranges = $( [ $range_start:literal ; $range_end:literal ] ),* ,
ranges = $( $range_start:literal ..= $range_end:literal ),* ,
is_fully_loaded = $is_fully_loaded:expr,
list_state = $list_state:ident,
}
@@ -1193,29 +1246,29 @@ mod tests {
list_state = NotLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [0; 9],
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = [10; 19],
ranges = 10..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms is reached!
next => {
ranges = [20; 24],
ranges = 20..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 24], // the range starts at 0 now!
ranges = 0..=24, // the range starts at 0 now!
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 24],
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1235,29 +1288,29 @@ mod tests {
list_state = NotLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [0; 9],
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = [10; 19],
ranges = 10..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms to fetch is reached!
next => {
ranges = [20; 21],
ranges = 20..=21,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 21], // the range starts at 0 now!
ranges = 0..=21, // the range starts at 0 now!
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 21],
ranges = 0..=21,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1277,29 +1330,29 @@ mod tests {
list_state = NotLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [0; 9],
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = [0; 19],
ranges = 0..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms is reached!
next => {
ranges = [0; 24],
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 24],
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 24],
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1319,29 +1372,29 @@ mod tests {
list_state = NotLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [0; 9],
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = [0; 19],
ranges = 0..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms is reached!
next => {
ranges = [0; 21],
ranges = 0..=21,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 21],
ranges = 0..=21,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 21],
ranges = 0..=21,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1363,18 +1416,18 @@ mod tests {
maximum_number_of_rooms = 25,
// The maximum number of rooms is reached directly!
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
}
@@ -1396,18 +1449,18 @@ mod tests {
maximum_number_of_rooms = 25,
// The maximum number of rooms is reached directly!
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = [0; 10], [42; 153],
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
}
@@ -1420,7 +1473,7 @@ mod tests {
list_state = PartiallyLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [3; 7],
ranges = 3..=7,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1433,7 +1486,7 @@ mod tests {
list_state = PartiallyLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [3; 7], [10; 23],
ranges = 3..=7, 10..=23,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1446,7 +1499,7 @@ mod tests {
list_state = PartiallyLoaded,
maximum_number_of_rooms = 25,
next => {
ranges = [42; 77],
ranges = 42..=77,
is_fully_loaded = true,
list_state = FullyLoaded,
},
@@ -1466,6 +1519,143 @@ mod tests {
};
}
#[test]
fn test_generator_changing_sync_mode_on_the_fly() {
let (sender, _receiver) = channel(4);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(SlidingSyncMode::new_selective())
.ranges(vec![0..=10, 42..=153].to_vec())
.build(sender);
assert_ranges! {
list = list,
list_state = NotLoaded,
maximum_number_of_rooms = 25,
// The maximum number of rooms is reached directly!
next => {
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = 0..=10, 42..=153,
is_fully_loaded = true,
list_state = FullyLoaded,
}
};
// Changing from `Selective` to `Growing`.
list.set_sync_mode(SlidingSyncMode::new_growing(10, None)).unwrap();
assert_ranges! {
list = list,
list_state = FullyLoaded, // it inherits the state of the previous sync-mode since no sync has been made yet.
maximum_number_of_rooms = 25,
next => {
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = 0..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms is reached!
next => {
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
};
// Changing from `Growing` to `Paging`.
list.set_sync_mode(SlidingSyncMode::new_paging(10, None)).unwrap();
assert_ranges! {
list = list,
list_state = FullyLoaded, // it inherits the state of the previous sync-mode since no sync has been made yet.
maximum_number_of_rooms = 25,
next => {
ranges = 0..=9,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
next => {
ranges = 10..=19,
is_fully_loaded = false,
list_state = PartiallyLoaded,
},
// The maximum number of rooms is reached!
next => {
ranges = 20..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = 0..=24, // the range starts at 0 now!
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = 0..=24,
is_fully_loaded = true,
list_state = FullyLoaded,
},
};
// Changing from `Paging` to `Selective`.
list.set_sync_mode(SlidingSyncMode::new_selective()).unwrap();
assert_eq!(list.state(), SlidingSyncState::FullyLoaded); // it inherits the state of the previous sync-mode.
// We need to update the ranges, of course, as they are not managed
// automatically anymore.
list.set_range(0..=100).unwrap();
assert_ranges! {
list = list,
list_state = PartiallyLoaded, // changing the ranges make the state to go `PartiallyLoaded`.
maximum_number_of_rooms = 25,
// The maximum number of rooms is reached directly!
next => {
ranges = 0..=100,
is_fully_loaded = true,
list_state = FullyLoaded,
},
// Now it's fully loaded, so the same request must be produced everytime.
next => {
ranges = 0..=100,
is_fully_loaded = true,
list_state = FullyLoaded,
},
next => {
ranges = 0..=100,
is_fully_loaded = true,
list_state = FullyLoaded,
}
};
}
#[tokio::test]
#[allow(clippy::await_holding_lock)]
async fn test_sliding_sync_inner_update_state_room_list_and_maximum_number_of_rooms() {

View File

@@ -152,7 +152,9 @@ impl SlidingSync {
.write()
.unwrap()
.insert(room_id, settings.unwrap_or_default());
self.inner.internal_channel_send(SlidingSyncInternalMessage::ContinueSyncLoop).await?;
self.inner
.internal_channel_send(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
.await?;
Ok(())
}
@@ -163,7 +165,9 @@ impl SlidingSync {
if self.inner.room_subscriptions.write().unwrap().remove(&room_id).is_some() {
// … then keep the unsubscription for the next request.
self.inner.room_unsubscriptions.write().unwrap().insert(room_id);
self.inner.internal_channel_send(SlidingSyncInternalMessage::ContinueSyncLoop).await?;
self.inner
.internal_channel_send(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
.await?;
}
Ok(())
@@ -230,7 +234,9 @@ impl SlidingSync {
list_builder: SlidingSyncListBuilder,
) -> Result<Option<SlidingSyncList>> {
let list = list_builder.build(self.inner.internal_channel.0.clone());
self.inner.internal_channel_send(SlidingSyncInternalMessage::ContinueSyncLoop).await?;
self.inner
.internal_channel_send(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
.await?;
Ok(self.inner.lists.write().unwrap().insert(list.name().to_owned(), list))
}
@@ -591,11 +597,11 @@ impl SlidingSync {
use SlidingSyncInternalMessage::*;
match internal_message {
None | Some(BreakSyncLoop) => {
None | Some(SyncLoopStop) => {
break;
}
Some(ContinueSyncLoop) => {
Some(SyncLoopSkipOverCurrentIteration) => {
continue;
}
}
@@ -685,9 +691,13 @@ impl SlidingSyncInner {
#[derive(Debug)]
enum SlidingSyncInternalMessage {
/// Instruct the sync loop to stop.
#[allow(unused)] // temporary
BreakSyncLoop,
ContinueSyncLoop,
SyncLoopStop,
/// Instruct the sync loop to skip over any remaining work in its iteration,
/// and to jump to the next iteration.
SyncLoopSkipOverCurrentIteration,
}
#[cfg(any(test, feature = "testing"))]