Merge branch 'fix/channel-cache-miss' into 'master'

fix(backend): Return cached Channel from channel_from_uuid_or_group

See merge request schmiddi-on-mobile/flare!214
This commit is contained in:
Julian Schmidhuber committed 2026-05-05 12:42:10 +00:00
commit 71fa50ba65
1 file changed
+56
+56
View File
@@ -490,6 +490,16 @@ impl Manager {
Thread::Contact(uuid)
};
// Fast path: if we already know this thread, return the cached
// `Channel` so callers see notify-signals (e.g. `is-typing`) on the
// same GObject instance the UI is bound to. Without this, the
// incoming `TypingMessage` handler would toggle `is-typing` on a
// freshly-built orphan `Channel` and the typing indicator would
// silently never light up for known contacts.
if let Some(cached) = self.imp().channels.borrow().get(&thread).cloned() {
return cached;
}
let contact = Contact::from_service_address(&uuid, self).await;
let channel = Channel::from_contact_or_group(contact, group, self).await;
channel.initialize_avatar().await;
@@ -977,3 +987,49 @@ mod imp {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use libsignal_service::prelude::Uuid;
use libsignal_service::protocol::ServiceId;
/// Regression test for the channel-cache miss in
/// [`Manager::channel_from_uuid_or_group`].
///
/// The function previously always built and returned a fresh
/// `Channel` GObject, even when one for the same thread already
/// lived in the cache. Property notifications (e.g. `is-typing`
/// for incoming `TypingMessage`s) were therefore emitted on an
/// orphan instance the UI was not bound to, and indicators
/// silently never reflected the change. This test asserts the
/// invariant that a cached thread short-circuits to the cached
/// `Channel` instance.
#[test]
fn cached_thread_returns_cached_channel() {
gtk::init().expect("gtk to initialize");
let manager: Manager = glib::Object::new();
let cached: Channel = glib::Object::new();
let uuid = Uuid::from_u128(0x1);
let thread = Thread::Contact(ServiceId::Aci(uuid.into()));
manager
.imp()
.channels
.borrow_mut()
.insert(thread, cached.clone());
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("tokio current-thread runtime to build");
let result = runtime
.block_on(manager.channel_from_uuid_or_group(ServiceId::Aci(uuid.into()), &None));
assert!(
cached.eq(&result),
"channel_from_uuid_or_group must return the cached `Channel` instance \
so notify-signals reach widgets bound to it"
);
}
}