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

feat(knocking): process knocked rooms separately during sync #4128

Merged
merged 1 commit into from
Oct 16, 2024

Conversation

jmartinesp
Copy link
Contributor

Changes

This PR contains 2 commits:

  1. The first one processes the membership state events in process_sliding_sync_room_membership, deserializes them and decides whether the resulting room should be a knock or invite one default to invite if not sure. Then it propagates the changes up the flow.
  2. Since we already have handle_stripped_state to parse the contents of the invite_state stripped state events it seemed better to reuse this and deserialize the events only once, then mark the room as invited or knocked in this method. Ideally this should be part of the inner fn BaseRoomInfo::handle_stripped_state_event, but that doesn't have access to the Room::state property.

Both solutions work AFAICT, the 1st one centralises the membership processing in a single point but it's more inefficient, the 2nd one does only one round of deserialization but hides the membership change a bit lower in the sliding sync processing flow. Let me know if you have any preferences and I'll use one of those versions, or propose a better alternative.

Existing tests have been extended to check the invite vs knock room state processing.

  • Public API changes documented in changelogs (optional)

Signed-off-by:

@jmartinesp jmartinesp requested a review from a team as a code owner October 15, 2024 15:06
@jmartinesp jmartinesp requested review from andybalaam and removed request for a team October 15, 2024 15:06
@jmartinesp
Copy link
Contributor Author

I just rebased the changes to avoid a use ... conflict, there aren't any other changes.

Copy link

codecov bot commented Oct 15, 2024

Codecov Report

Attention: Patch coverage is 88.88889% with 9 lines in your changes missing coverage. Please review.

Project coverage is 84.74%. Comparing base (30f3a3c) to head (8636c3a).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/matrix-sdk-base/src/client.rs 90.90% 3 Missing ⚠️
crates/matrix-sdk-base/src/sliding_sync/mod.rs 92.30% 3 Missing ⚠️
crates/matrix-sdk/src/sync.rs 62.50% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4128      +/-   ##
==========================================
+ Coverage   84.72%   84.74%   +0.01%     
==========================================
  Files         269      269              
  Lines       28779    28836      +57     
==========================================
+ Hits        24384    24437      +53     
- Misses       4395     4399       +4     

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

Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

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

Looks promising. I think I prefer the way it's handled in the first commit, especially since there might be a nice simplification there too.

);

let mut room_info = room.clone_info();
room_info.mark_as_invited();
Copy link
Member

Choose a reason for hiding this comment

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

mark_as_knocked?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

let membership_event_content = invite_state.iter().find_map(|raw| {
raw.deserialize().ok().iter().find_map(|e| {
if let AnyStrippedStateEvent::RoomMember(membership_event) = e {
if membership_event.sender == user_id {
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 double-check this logic, please: I think the sender for an invite would not be the user themselves, but rather, the person who sent the invite. So we'd need to take a look at the state_key, in this case. (If that's true, consider adding a test which would have failed because of this?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think this is an auto-completion typo 😬 . Nice catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

01b3bc9 should take care of the typo, 50f7fb0 adds more tests for corner cases with wrong membership events.

Comment on lines 566 to 570
let user_id = self
.session_meta()
.expect("Sliding sync shouldn't run without an authenticated user.")
.user_id
.to_owned();
Copy link
Member

Choose a reason for hiding this comment

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

Could we pass this user_id as a parameter, to avoid repeating the work for each room?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

01b3bc9 should take care of this

// We need to find the membership event since it could be for either an invited
// or knocked room
let membership_event_content = invite_state.iter().find_map(|raw| {
raw.deserialize().ok().iter().find_map(|e| {
Copy link
Member

Choose a reason for hiding this comment

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

So I'm not sure we're looking in the right place here: the required_state events have already been deserialized and passed as a function parameter called state_events. Might be worth checking against a real-world Synapse, but I'd expect the membership event to be there instead. If that's true, then we don't need the deserialization step here, and this solution feels better than the second one IMO (with the previous sync protocol, knocked rooms would appear in the knocked subset of the response, which is nice and simple — I'd rather not change the room state based on extra other information).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Invite and Knock rooms use invite_state instead of required_state, I suspect their required_state would be empty, but I can double check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, there's no required_state for knocked rooms:

"!luXevcpKTZauFIpPnL:matrix.org": {
  "bump_stamp": 21538303,
  "notification_count": 0,
  "highlight_count": 0,
  "initial": true,
  "invite_state": [
    {
      "content": {
        "creator": "@jorgem_test:matrix.org",
        "room_version": "7"
      },
      "sender": "@jorgem_test:matrix.org",
      "state_key": "",
      "type": "m.room.create"
    },
    {
      "content": {
        "join_rule": "knock"
      },
      "sender": "@jorgem_test:matrix.org",
      "state_key": "",
      "type": "m.room.join_rules"
    },
    {
      "content": {
        "name": "Knocking room test"
      },
      "sender": "@jorgem_test:matrix.org",
      "state_key": "",
      "type": "m.room.name"
    },
    {
      "content": {
        "algorithm": "m.megolm.v1.aes-sha2"
      },
      "sender": "@jorgem_test:matrix.org",
      "state_key": "",
      "type": "m.room.encryption"
    },
    {
      "type": "m.room.member",
      "state_key": "@jorgem:element.io",
      "content": {
        "avatar_url": "mxc://element.io/75d9a551895233d284aff390d2086814a2f7e875",
        "displayname": "Jorge",
        "membership": "knock"
      },
      "sender": "@jorgem:element.io"
    }
  ]
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another option would be to handle the stripped state events in invite_state as part of the membership in this same method, but I think that's not possible because we need the push_rules too which are processed after this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could also deserialize the stripped state events in process_sliding_sync_room and pass it down both functions, this one and handle_invited_state, so they'd be deserialized just once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried this approach ⬆️ in 0b1ef5b

Copy link
Contributor

@andybalaam andybalaam left a comment

Choose a reason for hiding this comment

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

Some suggestions, all optional. Looks good to me.

@@ -527,30 +527,20 @@ impl BaseClient {
pub(crate) async fn handle_invited_state(
&self,
room: &Room,
events: &[Raw<AnyStrippedStateEvent>],
events: &[(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)],
Copy link
Contributor

Choose a reason for hiding this comment

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

Some documentation of the meaning of this parameter would be appreciated. It's a bit cryptic at the moment: a slice of pairs of things that look the same at first glance.

@@ -1042,6 +1090,76 @@ mod tests {
assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
}

#[async_test]
async fn test_knocked_room_membership_event() {
Copy link
Contributor

Choose a reason for hiding this comment

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

test_receiving_a_knocked_room_membership_event_creates_a_knocked_room ?

}

#[async_test]
async fn test_knocked_room_membership_event_with_wrong_state_key() {
Copy link
Contributor

Choose a reason for hiding this comment

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

test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room ?

}

#[async_test]
async fn test_unknown_room_membership_event_in_invite_state() {
Copy link
Contributor

Choose a reason for hiding this comment

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

test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room ?

@jmartinesp
Copy link
Contributor Author

Thanks for the feedback @andybalaam , I'll double check with @bnjbvr if these changes are what we want for the feature 🤞 .

Copy link
Member

@bnjbvr bnjbvr left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM! only minor nits, so unless something big comes up, feel free to address these (or not) and merge. Good job!

.filter_map(|raw_event| match raw_event.deserialize() {
Ok(event) => Some((raw_event.clone(), event)),
Err(e) => {
warn!("Couldn't deserialize state event: {e}");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
warn!("Couldn't deserialize state event: {e}");
warn!("Couldn't deserialize stripped state event: {e}");

Comment on lines +368 to +371
Option<JoinedRoomUpdate>,
Option<LeftRoomUpdate>,
Option<InvitedRoom>,
Option<KnockedRoom>,
Copy link
Member

Choose a reason for hiding this comment

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

(god what have we done here 😨)(no need to change anything right now, unless you see an obvious better way here)

@@ -428,7 +457,8 @@ impl BaseClient {

let push_rules = self.get_push_rules(account_data_processor).await?;

if let Some(invite_state) = &room_data.invite_state {
// This will be used for both invited and knocked rooms
Copy link
Member

Choose a reason for hiding this comment

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

uber-nit:

Suggested change
// This will be used for both invited and knocked rooms
// This will be used for both invited and knocked rooms.

state_events: &[AnySyncStateEvent],
invite_state: &Option<Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>>,
Copy link
Member

Choose a reason for hiding this comment

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

nit: in Rust it's more idiomatic to pass Option<&T> rather than &Option<T>, by calling as_ref() at the caller's call-site.

Alternatively, you could make this non Optional and have the caller .unwrap_or_default().

Copy link
Member

Choose a reason for hiding this comment

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

Could we name this stripped_state, though, since it applies to both invites and knocked rooms?

Comment on lines 1376 to 1378
"sender": "@alice:example.com",
"type": "m.room.member",
"state_key": "@bob:example.com",
Copy link
Member

Choose a reason for hiding this comment

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

Can someone knock on behalf of somebody else? 🤔 Should the sender/state_key be the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooops. Copy&paste issue 😅 .

@jmartinesp jmartinesp merged commit 664f6d5 into main Oct 16, 2024
40 checks passed
@jmartinesp jmartinesp deleted the feat/add-knocked-rooms-sync branch October 16, 2024 14:12
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.

3 participants