Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(event cache): remove an empty events chunk after pushing a gap in sync #4734

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

bnjbvr
Copy link
Member

@bnjbvr bnjbvr commented Feb 27, 2025

Small optimization: when we have an empty events chunk, and we're about to push a gap, remove the empty events chunk. It's the same code as for removing a gap chunk, so not much new code is introduced in the linked chunk code (nice!). This makes back-pagination indicate we've reached the start of the timeline quicker, because now they don't have to load an initial empty chunk.

Also, get rid of EmptyChunkRule, as it was always set to ::Remove in the code, and only set to ::Keep in tests. In the worst case where we'd find that having ::Keep is useful, we can still revert the commit individually in the future.

Part of #3280.

The linked chunk always starts with an empty events chunk. If we receive
a gap from sync, then we will immediately push a gap chunk; in this
case, it might be better to replace the events chunk with a gap chunk.
This is equivalent to removing the empty events chunk, after pushing
back the first one (we can't do it before, otherwise we might get rid of
the only chunk in the linked chunk, which breaks the invariant that a
linked chunk is never empty).
…y used in testing

This isn't useful to keep, since it's only used in testing. Worst case,
we can revert this commit in the future.
@bnjbvr bnjbvr requested a review from a team as a code owner February 27, 2025 17:54
@bnjbvr bnjbvr requested review from poljar and removed request for a team February 27, 2025 17:54
Copy link

codecov bot commented Feb 27, 2025

Codecov Report

Attention: Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.

Project coverage is 86.14%. Comparing base (4742aa2) to head (960bc84).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/matrix-sdk-common/src/linked_chunk/mod.rs 87.50% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4734   +/-   ##
=======================================
  Coverage   86.14%   86.14%           
=======================================
  Files         291      291           
  Lines       34308    34316    +8     
=======================================
+ Hits        29553    29560    +7     
- Misses       4755     4756    +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Hywan Hywan requested review from Hywan and removed request for poljar February 28, 2025 09:04
Copy link
Member

@Hywan Hywan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of feedback, but great work!

@@ -687,7 +694,7 @@ impl<const CAP: usize, Item, Gap> LinkedChunk<CAP, Item, Gap> {
///
/// This returns the next insert position, viz. the start of the next
/// chunk, if any, or none if there was no next chunk.
pub fn remove_gap_at(
pub fn remove_empty_chunk_at(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Github doesn't allow me to attach a comment at line 693 but the comment of this method has to be updated:

- Remove a gap with the given identifier.
+ Remove a chunk with the given identifier if and only if it is empty.
+
+ A chunk is considered empty if: it contains zero items, or is a gap.

};
match chunk.content() {
ChunkContent::Items(items) if !items.is_empty() => {
if chunk.is_items() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We know it's an Items chunk already. Remove this condition.

Comment on lines +706 to +715
match chunk.content() {
ChunkContent::Items(items) if !items.is_empty() => {
if chunk.is_items() {
return Err(Error::RemovingNonEmptyItemsChunk { identifier: chunk_identifier });
};
}
_ => {
// Either a gap or an empty items chunk: continue.
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can rewrite this full block into this:

if chunk.len() > 0 {
    return Err(Error::RemovingNonEmptyItemsChunk {});
}

Chunk::len returns 0 for a Gap, or the number of items for an Items.

let err = linked_chunk.remove_gap_at(ChunkIdentifier(0)).unwrap_err();
assert_matches!(err, Error::ChunkIsItems { .. });
// Try to remove a chunk that's not empty.
let err = linked_chunk.remove_empty_chunk_at(ChunkIdentifier(0)).unwrap_err();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, can you rename the test from test_remove_gap_at to test_remove_empty_chunk_at?

// It was the last chunk, so there's no next insert position.
assert!(next.is_none());
assert_items_eq!(linked_chunk, [-] ['a', 'b'] ['l', 'm']);
assert_eq!(linked_chunk.updates().unwrap().take(), &[RemoveChunk(ChunkIdentifier(4))]);

// Remove the gap at the beginning.
let maybe_next = linked_chunk.remove_gap_at(ChunkIdentifier(1)).unwrap();
let maybe_next = linked_chunk.remove_empty_chunk_at(ChunkIdentifier(1)).unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update the test to remove an empty Items chunk too please? Simply change a Gap to an Items in the test data (+ updating inline test comments) and it would be good.

@@ -687,7 +694,7 @@ impl<const CAP: usize, Item, Gap> LinkedChunk<CAP, Item, Gap> {
///
/// This returns the next insert position, viz. the start of the next
/// chunk, if any, or none if there was no next chunk.
pub fn remove_gap_at(
pub fn remove_empty_chunk_at(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, can you update your patch to include a new check:

⚠️ The LinkedChunk MUST contain at least 2 chunks before removing one. Otherwise the LinkedChunk will be empty, and it violates a constraint.

We can remove this constraint, but it involves a bit more work. Also we don't need for the moment.

Comment on lines +562 to +569
room_events.rchunks().next().and_then(|chunk| {
match chunk.content() {
ChunkContent::Items(events) if events.is_empty() => {
Some(chunk.identifier())
}
_ => None,
}
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion. Can be rewritten like so:

room_events.rchunks().next().and_then(|chunk| (chunk.is_items() && chunk.len() == 0).then_some(chunk.identifier());

(it may involve changing the visibility of Chunk::len() to pub).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants