Skip to content

Conversation

@ZhangHanDong
Copy link
Contributor

@ZhangHanDong ZhangHanDong commented Oct 22, 2025

Fixed issue #506

This is a remastered version of the PR #550 .

  • Add a lightweight cpu_worker thread that stays alive for the app lifetime and runs CPU-bound jobs (currently the room-member search); App::handle_startup initializes it and widgets enqueue work via cpu_worker::spawn_cpu_job.
  • Rework MentionableTextInput into an explicit state machine with search IDs, cancellation tokens, loading/empty UI states, and streaming updates that arrive over the background worker channel.
  • Replace the old MatrixRequest::SearchRoomMembers path: sliding_sync now fetches joined-member lists directly, emits RoomMembersListFetched, and RoomScreen caches those members plus precomputed sort data for downstream widgets.
  • Refactor room/member_search.rs to support batched streaming, cancellation, richer match heuristics, optional precomputed sort reuse, and add unit tests for the new helpers.

There are three related issues that still need to be fixed.

@ZhangHanDong ZhangHanDong added the waiting-on-review This issue is waiting to be reviewed label Oct 22, 2025
@kevinaboos
Copy link
Member

@alanpoon & @tyreseluo, can you please give this a review before I dig in? Thanks.

@tyreseluo
Copy link
Contributor

@alanpoon & @tyreseluo, can you please give this a review before I dig in? Thanks.

yep, i will.

…tartup so CPU-bound work like member searches runs off the UI thread.

  - Reworked mention search to enqueue jobs on the CPU worker with cancellation tokens, unique search IDs, and better handling of loading state when the member list is still fetching.
  - Updated room loading and sync flow to fetch the full member list from the homeserver and deliver it through a new RoomMembersListFetched timeline update, removing the old MatrixRequest::SearchRoomMembers path.
  - Refactored the member search algorithm for batching, cancellation, richer match strategies, and added unit tests for the new helpers.
@ZhangHanDong ZhangHanDong added waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-review This issue is waiting to be reviewed labels Oct 25, 2025
@ZhangHanDong ZhangHanDong added waiting-on-review This issue is waiting to be reviewed and removed waiting-on-author This issue is waiting on the original author for a response labels Oct 26, 2025
@ZhangHanDong
Copy link
Contributor Author

Review suggestions:
• Select a large room (thousands or tens of thousands of members), @ a user, and search.
• Continue testing in rooms with more than 50 members.
• Continue testing in rooms with fewer than 50 members.
• Close large/small rooms, reopen them, and continue testing.
• Logout, then login again, and rerun the above four steps.

Copy link
Contributor

@alanpoon alanpoon left a comment

Choose a reason for hiding this comment

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

  1. Overall the performance of loading the mentionable list seems to be impacted.
  2. There's seem too many fields added MentionableTextInput.
  3. There's also bug when clicking the user from mentionable list, it does not apply into the text box.
  4. The search does not work after "comma".
searching_after comma 5. There should not be "Notify the Room" for direct message. Screenshot 2025-10-29 at 5 29 29 PM

Comment on lines +680 to +693
match timeline.room().members(RoomMemberships::JOIN).await {
Ok(members) => {
let count = members.len();
log!("Fetched {count} members for room {room_id} after sync.");
if let Err(err) = sender.send(TimelineUpdate::RoomMembersListFetched { members }) {
warning!("Failed to send RoomMembersListFetched update for room {room_id}: {err:?}");
} else {
SignalToUI::set_ui_signal();
}
}
Err(err) => {
warning!("Failed to fetch room members from server for room {room_id}: {err:?}");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There is already timeline.fetched_members above. This looks like a redundant call. There is extra matrix sqlite call. This could explain why this PR is slower when loading the mentionable list.
Screenshot 2025-10-29 at 1 13 24 PM It looks like the mentionable list still loads even this change is reverted.

// Note: This can be sent from either GetRoomMembers (local cache lookup)
// or SyncRoomMemberList (full server sync). We only clear the sync pending
// flag when we receive RoomMembersSynced (which is only sent after full sync).
let sort_data = precompute_member_sort(&members);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to move this processing operation to async function in sliding_sync rather than in UI thread?

}

/// Search room members in background thread with streaming support (backward compatible)
pub fn search_room_members_streaming(
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is not used.

pub sender: Sender<SearchResult>,
pub search_id: u64,
pub precomputed_sort: Option<Arc<PrecomputedMemberSort>>,
pub cancel_token: Option<Arc<AtomicBool>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

The PR is significantly slow enough when loading the mentionable list to test the cancel token when there is a new search input. The cancel token does not seem to work.

  1. Go to room with large number of members. (mentionable list is loading)
  2. While it is loading, type "k". I am expecting the mentionable list to load faster for shorter query.
  3. After it takes several seconds to load "k" result, remove "k" to go to empty string queries.
  4. I am expecting mentionable list to display "loading". But it shows the full results.
Screenshot 2025-10-29 at 2 49 41 PM

Comment on lines +60 to +62
cx.spawn_thread(move || match job {
CpuJob::SearchRoomMembers(params) => run_member_search(params),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a way to close this CpuJob when closing the room screen tab?

sender: &Sender<SearchResult>,
cancel_token: &Option<Arc<AtomicBool>>,
search_id: u64,
search_text: &Arc<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for Arc

if self.is_searching() {
if let Event::KeyUp(key_event) = event {
if key_event.key_code == KeyCode::Escape {
self.cancel_active_search();
Copy link
Contributor

Choose a reason for hiding this comment

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

After pressing KeyCode::Escaope, it is not cancelled. is_cancelled does not return true,

Comment on lines +454 to +456
// ESC key is now handled in the main event handler using KeyUp event
// This avoids conflicts with escaped() method being consumed by other components

Copy link
Contributor

Choose a reason for hiding this comment

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

can be removed

// Force a fresh search now that members are available
let search_text = self.cmd_text_input.search_text();
self.last_search_text = None;
self.update_user_list(cx, &search_text, scope);
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a cyclic dependencies. MentionableTextInputAction::RoomMembersLoaded -> "update_user_list" -> submit_async_request(MatrixRequest::GetRoomMembers) -> TimelineUpdate::RoomMembersListFetched -> MentionableTextInputAction::RoomMembersLoaded

Comment on lines +1084 to +1097
if is_complete {
self.search_results_pending = false;
// Search is complete - get results for final UI update
let final_results = if let MentionSearchState::Searching {
accumulated_results,
..
} = &self.search_state
{
accumulated_results.clone()
} else {
Vec::new()
};

if final_results.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

For some reason, when it is loading, the search result is complete but is final results is empty. Hence result the overall code has several checks.
Screenshot 2025-10-29 at 4 13 32 PM

@ZhangHanDong ZhangHanDong added waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-review This issue is waiting to be reviewed labels Oct 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-author This issue is waiting on the original author for a response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants