Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-18 - Optimized space sync async N+1 fetching
**Learning:** In space service synchronization, sequential async execution (awaiting each avatar fetch in a loop for `VectorDiff::Append`, `VectorDiff::Reset`, or initial synchronization) caused a significant N+1 query problem that blocked rendering the list until all were complete.
**Action:** Use `futures_util::future::join_all` on an iterator of futures to run asynchronous fetches (like avatar requests) concurrently while resolving sequentially to preserve the correct UI order.
33 changes: 24 additions & 9 deletions src/space_service_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::{collections::{HashMap, HashSet, hash_map::Entry}, iter::Peekable, sync::Arc};
use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use futures_util::{future::join_all, StreamExt};
use imbl::Vector;
use makepad_widgets::*;
use matrix_sdk::{Client, RoomState, media::MediaRequestParameters};
Expand Down Expand Up @@ -130,9 +130,12 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> {

// Get the set of top-level (root) spaces that the user has joined.
let (initial_spaces, mut spaces_diff_stream) = space_service.subscribe_to_top_level_joined_spaces().await;
for space in &initial_spaces {
add_new_space(space, &client).await;

let info_futures = initial_spaces.iter().map(|space| get_joined_space_info(space, &client));
for jsi in join_all(info_futures).await {
enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi));
}

let mut all_joined_spaces: Vector<SpaceRoom> = initial_spaces;
if LOG_SPACE_SERVICE_DIFFS { log!("space_service: initial set: {all_joined_spaces:?}"); }

Expand Down Expand Up @@ -224,8 +227,13 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> {
match diff {
VectorDiff::Append { values: new_spaces } => {
if LOG_SPACE_SERVICE_DIFFS { log!("space_service: diff Append {}", new_spaces.len()); }

let info_futures = new_spaces.iter().map(|space| get_joined_space_info(space, &client));
for jsi in join_all(info_futures).await {
enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi));
}

for new_space in new_spaces {
add_new_space(&new_space, &client).await;
all_joined_spaces.push_back(new_space);
}
}
Expand Down Expand Up @@ -315,9 +323,12 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> {
remove_space(&space);
}
enqueue_spaces_list_update(SpacesListUpdate::ClearSpaces);
for new_space in &new_spaces {
add_new_space(new_space, &client).await;

let info_futures = new_spaces.iter().map(|space| get_joined_space_info(space, &client));
for jsi in join_all(info_futures).await {
enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi));
}

all_joined_spaces = new_spaces;
}
}
Expand All @@ -334,7 +345,7 @@ pub async fn space_service_loop(client: Client) -> anyhow::Result<()> {
}


async fn add_new_space(space: &SpaceRoom, client: &Client) {
async fn get_joined_space_info(space: &SpaceRoom, client: &Client) -> JoinedSpaceInfo {
let space_avatar_opt = if let Some(url) = &space.avatar_url {
fetch_space_avatar(url.clone(), client)
.await
Expand All @@ -345,7 +356,7 @@ async fn add_new_space(space: &SpaceRoom, client: &Client) {
|| utils::avatar_from_room_name(Some(&space.display_name))
);

let jsi = JoinedSpaceInfo {
JoinedSpaceInfo {
space_name_id: RoomNameId::new(
matrix_sdk::RoomDisplayName::Named(space.display_name.clone()),
space.room_id.clone(),
Expand All @@ -358,7 +369,11 @@ async fn add_new_space(space: &SpaceRoom, client: &Client) {
world_readable: space.world_readable,
guest_can_join: space.guest_can_join,
children_count: space.children_count,
};
}
}

async fn add_new_space(space: &SpaceRoom, client: &Client) {
let jsi = get_joined_space_info(space, client).await;
enqueue_spaces_list_update(SpacesListUpdate::AddJoinedSpace(jsi));
}

Expand Down
5 changes: 2 additions & 3 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub fn load_png_or_jpg(img: &ImageRef, cx: &mut Cx, data: &[u8]) -> Result<(), I
.or_else(|_| img.load_jpg_from_data(cx, data))
}

let res = match imghdr::from_bytes(data) {
match imghdr::from_bytes(data) {
Some(imghdr::Type::Png) => img.load_png_from_data(cx, data),
Some(imghdr::Type::Jpeg) => img.load_jpg_from_data(cx, data),
Some(unsupported) => {
Expand All @@ -123,7 +123,7 @@ pub fn load_png_or_jpg(img: &ImageRef, cx: &mut Cx, data: &[u8]) -> Result<(), I
ImageError::UnsupportedFormat
})
}
};
}
// Disabled: dumping invalid user-selected image bytes can duplicate private or large files.
// if let Err(err) = res.as_ref() {
// // debugging: dump out the bad image to disk
Expand All @@ -141,7 +141,6 @@ pub fn load_png_or_jpg(img: &ImageRef, cx: &mut Cx, data: &[u8]) -> Result<(), I
// let _ = std::fs::write(path, data)
// .inspect_err(|e| error!("Failed to write bad image to disk: {e}"));
// }
res
}


Expand Down
Loading