diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index 8f73e04e..2600026f 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -233,7 +233,7 @@ impl ChatRegistry { if event.kind == Kind::InboxRelays { let current_user = signer.get_public_key_async().await?; if event.pubkey == current_user { - tx.send_async(Signal::InboxReady).await?; + tx.send_async(Signal::InboxReady).await.ok(); } } @@ -262,24 +262,24 @@ impl ChatRegistry { if rumor.tags.is_empty() { let signal = Signal::error(&event, "Recipient is missing"); - tx.send_async(signal).await?; + tx.send_async(signal).await.ok(); } // Emit message for both new and backlog events let signal = Signal::message(event.id, rumor); - tx.send_async(signal).await?; + tx.send_async(signal).await.ok(); } Err(e) => { let reason = format!("Failed to extract rumor: {e}"); let signal = Signal::error(event.as_ref(), reason); - tx.send_async(signal).await?; + tx.send_async(signal).await.ok(); } } } RelayMessage::EndOfStoredEvents(id) if (id.as_ref() == &sub_id1 || id.as_ref() == &sub_id2) => { - tx.send_async(Signal::Eose).await?; + tx.send_async(Signal::Eose).await.ok(); } _ => {} } @@ -290,7 +290,11 @@ impl ChatRegistry { self.signal_consumer = Some(cx.spawn(async move |this, cx| { while let Ok(message) = rx.recv_async().await { - this.update(cx, |this, cx| { + // `update_in` (rather than `update`) routes through a + // try-borrow: on wasm a task poll that lands while the app + // context is borrowed can't panic and kill this consumer + // (which would stall all message delivery). + this.update_in(cx, |this, _window, cx| { // Drain the whole queue in a single update so a burst of // events (e.g. history sync after login) collapses into // one repaint instead of one per message (important on @@ -374,7 +378,7 @@ impl ChatRegistry { .is_some(); if !found { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(ChatEvent::InboxRelayNotFound); })?; } @@ -425,7 +429,7 @@ impl ChatRegistry { }); if let Err(e) = task.await { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(ChatEvent::Error(e.to_string())); })?; } @@ -434,6 +438,7 @@ impl ChatRegistry { })); } + /// Get all messages for the provided signer /// Reload the chat registry, fetching messages and contact list from relays. pub fn reload(&mut self, cx: &mut Context) { self.reset(cx); @@ -638,13 +643,13 @@ impl ChatRegistry { self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(rooms) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.extend_rooms(rooms, cx); this.sort(cx); })?; } Err(e) => { - this.update(cx, |_, cx| { + this.update_in(cx, |_, _window, cx| { cx.emit(ChatEvent::Error(e.to_string())); })?; } diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 437fcb8e..f3c3b047 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -279,7 +279,7 @@ impl ChatPanel { } } } - this.update(cx, |_, cx| cx.notify()).ok(); + this.update_in(cx, |_, _window, cx| cx.notify()).ok(); } Ok(()) })); @@ -336,8 +336,10 @@ impl ChatPanel { self.tasks.push(cx.spawn(async move |this, cx| { let events = get_messages.await?; - // Update message list - this.update(cx, |this, cx| { + // Update message list. `update_in` (rather than `update`) routes + // through a try-borrow: on wasm a task poll that lands while the + // app context is borrowed can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.insert_messages(&events, cx); })?; @@ -479,7 +481,10 @@ impl ChatPanel { let mut sent_ids = sent_ids.lock().await; sent_ids.extend(outputs.iter().filter_map(|output| output.gift_wrap_id)); - this.update(cx, |this, cx| { + // `update_in` (rather than `update`) routes through a try-borrow: + // on wasm a poll that lands while the app context is borrowed + // can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.insert_reports(id, outputs, cx); })?; @@ -650,7 +655,7 @@ impl ChatPanel { }); self.tasks.push(cx.spawn_in(window, async move |this, cx| { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_uploading(true, cx); })?; diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 84f9b079..f8e61ad3 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -184,7 +184,7 @@ impl DeviceRegistry { } // New response event from the master device Kind::Custom(4455) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.extract_encryption(event, cx); })?; } @@ -272,7 +272,7 @@ impl DeviceRegistry { return Ok(()); } - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(DeviceEvent::NotSet); })?; @@ -287,13 +287,13 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(keys) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_signer(keys, cx); this.wait_for_request(cx); })?; } Err(e) => { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(DeviceEvent::error(e.to_string())); })?; } @@ -358,12 +358,12 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { if let Ok(keys) = task.await { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_signer(keys, cx); this.wait_for_request(cx); })?; } else { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.request(cx); })?; } @@ -439,17 +439,17 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(Some(event)) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.extract_encryption(event, cx); })?; } Ok(None) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.wait_for_approval(cx); })?; } Err(e) => { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(DeviceEvent::error(e.to_string())); })?; } @@ -508,12 +508,12 @@ impl DeviceRegistry { self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(keys) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_signer(keys, cx); })?; } Err(e) => { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(DeviceEvent::error(e.to_string())); })?; } diff --git a/crates/person/src/lib.rs b/crates/person/src/lib.rs index b0fe6bb0..1757bfc1 100644 --- a/crates/person/src/lib.rs +++ b/crates/person/src/lib.rs @@ -80,7 +80,11 @@ impl PersonRegistry { tasks.push(cx.spawn(async move |this, cx| { while let Ok(event) = rx.recv_async().await { - this.update(cx, |this, cx| { + // `update_in` (rather than `update`) routes through a + // try-borrow: on wasm a task poll that lands while the app + // context is borrowed can't panic and kill this consumer + // (which would stall the whole metadata pipeline). + this.update_in(cx, |this, _window, cx| { // Drain the whole queue in a single update so a burst of // events collapses into one repaint instead of one per // event (important on wasm, where everything runs on the @@ -231,7 +235,7 @@ impl PersonRegistry { self.tasks.push(cx.spawn(async move |this, cx| { if let Ok(persons) = task.await { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.bulk_insert(persons, cx); }) .ok(); diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index c03c5505..d9afb86a 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -181,7 +181,7 @@ impl NostrRegistry { let task = cx.spawn(async move |this, cx| { match new_signer.get_public_key_async().await { Ok(public_key) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.signer.swap_inner(new_signer); this.current_user = Some(public_key); cx.emit(StateEvent::SignerChanged); @@ -189,7 +189,7 @@ impl NostrRegistry { })?; } Err(e) => { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(StateEvent::error(e.to_string())); })?; } @@ -226,7 +226,7 @@ impl NostrRegistry { self.tasks.push(cx.spawn(async move |this, cx| { if let Err(e) = task.await { - this.update(cx, |_this, cx| { + this.update_in(cx, |_this, _window, cx| { cx.emit(StateEvent::error(e.to_string())); })?; } @@ -248,7 +248,7 @@ impl NostrRegistry { let secret_key = SecretKey::parse(&content)?; let keys = Keys::new(secret_key); - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_signer(keys, cx); cx.notify(); })?; @@ -263,19 +263,19 @@ impl NostrRegistry { // Handle auth url with the default browser signer.auth_url_handler(CoopAuthUrlHandler); - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_signer(signer, cx); cx.notify(); })?; } else if content == "proxy" { #[cfg(not(target_arch = "wasm32"))] - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.connect_proxy(cx); })?; } } _ => { - this.update(cx, |_, cx| { + this.update_in(cx, |_, _window, cx| { cx.emit(StateEvent::NoSigner); })?; } @@ -352,7 +352,7 @@ impl NostrRegistry { let proxy = proxy.clone(); async move |this, cx| { while let Ok(url) = rx.recv_async().await { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { let save = cx.write_credentials(USER_KEYRING, "proxy", b"proxy"); cx.background_spawn(async move { save.await.ok() }).detach(); cx.open_url(&url); @@ -374,7 +374,7 @@ impl NostrRegistry { loop { executor.timer(Duration::from_secs(5)).await; if !proxy.is_session_active() { - _ = this.update(cx, |this, cx| { + _ = this.update_in(cx, |this, _window, cx| { // Only notify if this proxy is still the active signer if this.current_user.is_some() { this.signer.swap_inner(Keys::generate()); diff --git a/crates/ui/src/checkbox.rs b/crates/ui/src/checkbox.rs index 1ce8e19e..a1645074 100644 --- a/crates/ui/src/checkbox.rs +++ b/crates/ui/src/checkbox.rs @@ -1,16 +1,16 @@ use std::rc::Rc; -use instant::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ - div, px, relative, rems, svg, Animation, AnimationExt, AnyElement, App, Div, ElementId, - InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString, - StatefulInteractiveElement, StyleRefinement, Styled, Window, + Animation, AnimationExt, AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, + ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, + Window, div, px, relative, rems, svg, }; +use instant::Duration; use theme::ActiveTheme; use crate::icon::IconNamed; -use crate::{v_flex, Disableable, IconName, Selectable, Sizable, Size, StyledExt as _}; +use crate::{Disableable, IconName, Selectable, Sizable, Size, StyledExt as _, v_flex}; /// A Checkbox element. #[allow(clippy::type_complexity)] @@ -172,10 +172,16 @@ pub(crate) fn checkbox_check_icon( if !disabled && checked != *toggle_state.read(cx) { let duration = Duration::from_secs_f64(0.25); cx.spawn({ - let toggle_state = toggle_state.clone(); + let toggle_state = toggle_state.downgrade(); async move |cx| { cx.background_executor().timer(duration).await; - toggle_state.update(cx, |this, _| *this = checked); + // `update_in` (rather than `update`) routes through a + // try-borrow: on wasm a task poll that lands while + // the app context is borrowed can't panic and kill + // this task. + toggle_state + .update_in(cx, |this, _window, _| *this = checked) + .ok(); } }) .detach(); diff --git a/crates/ui/src/input/blink_cursor.rs b/crates/ui/src/input/blink_cursor.rs index a0c2c979..da9b03f5 100644 --- a/crates/ui/src/input/blink_cursor.rs +++ b/crates/ui/src/input/blink_cursor.rs @@ -1,6 +1,5 @@ -use instant::Duration; - use gpui::{Context, Pixels, Task, px}; +use instant::Duration; static INTERVAL: Duration = Duration::from_millis(500); static PAUSE_DELAY: Duration = Duration::from_millis(300); @@ -63,9 +62,11 @@ impl BlinkCursor { let epoch = self.next_epoch(); self._task = cx.spawn(async move |this, cx| { cx.background_executor().timer(INTERVAL).await; - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| this.blink(epoch, cx)); - } + // `update_in` (rather than `update`) routes through a try-borrow: + // on wasm a task poll that lands while the app context is + // borrowed can't panic and kill this recurring task. + this.update_in(cx, |this, _window, cx| this.blink(epoch, cx)) + .ok(); }); } @@ -85,12 +86,11 @@ impl BlinkCursor { self._task = cx.spawn(async move |this, cx| { cx.background_executor().timer(PAUSE_DELAY).await; - if let Some(this) = this.upgrade() { - this.update(cx, |this, cx| { - this.paused = false; - this.blink(epoch, cx); - }); - } + this.update_in(cx, |this, _window, cx| { + this.paused = false; + this.blink(epoch, cx); + }) + .ok(); }); } } diff --git a/crates/workspace/src/dialogs/import.rs b/crates/workspace/src/dialogs/import.rs index b9f3d379..f2ba0e04 100644 --- a/crates/workspace/src/dialogs/import.rs +++ b/crates/workspace/src/dialogs/import.rs @@ -129,7 +129,7 @@ impl ImportIdentity { })?; } Err(e) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_error(e.to_string(), cx); })?; } @@ -198,7 +198,7 @@ impl ImportIdentity { self.tasks.push(cx.spawn(async move |this, cx| { cx.background_executor().timer(Duration::from_secs(3)).await; - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.error.update(cx, |this, cx| { *this = None; cx.notify(); @@ -271,7 +271,7 @@ impl Render for ImportIdentity { this.login(window, cx); })), ) - .when(cfg!(target_arch = "wasm32"), |this| this.child(divider(cx))) + .child(divider(cx)) .when(!is_wasm, |this| { this.child( Button::new("proxy") diff --git a/crates/workspace/src/dialogs/restore.rs b/crates/workspace/src/dialogs/restore.rs index 958d387b..45babfbd 100644 --- a/crates/workspace/src/dialogs/restore.rs +++ b/crates/workspace/src/dialogs/restore.rs @@ -83,7 +83,7 @@ impl RestoreEncryption { self.tasks.push(cx.spawn(async move |this, cx| { cx.background_executor().timer(Duration::from_secs(3)).await; - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.error.update(cx, |this, cx| { *this = None; cx.notify(); diff --git a/crates/workspace/src/dialogs/screening.rs b/crates/workspace/src/dialogs/screening.rs index 79738788..6a26049a 100644 --- a/crates/workspace/src/dialogs/screening.rs +++ b/crates/workspace/src/dialogs/screening.rs @@ -105,7 +105,7 @@ impl Screening { self.tasks.push(cx.spawn(async move |this, cx| { let result = task.await.unwrap_or(false); - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.followed = result; cx.notify(); }) @@ -139,7 +139,7 @@ impl Screening { self.tasks.push(cx.spawn(async move |this, cx| { match task.await { Ok(contacts) => { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.mutual_contacts = contacts; cx.notify(); }) @@ -185,7 +185,7 @@ impl Screening { self.tasks.push(cx.spawn(async move |this, cx| { let result = task.await; - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.last_active = result; cx.notify(); }) @@ -208,7 +208,7 @@ impl Screening { self.tasks.push(cx.spawn(async move |this, cx| { let result = task.await.unwrap_or(false); - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.verified = result; cx.notify(); }) diff --git a/crates/workspace/src/panels/backup.rs b/crates/workspace/src/panels/backup.rs index 51dd1cae..36d33559 100644 --- a/crates/workspace/src/panels/backup.rs +++ b/crates/workspace/src/panels/backup.rs @@ -97,7 +97,7 @@ impl BackupPanel { cx.background_executor().timer(Duration::from_secs(2)).await; // Clear the error message after a delay - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_copied(false, cx); })?; diff --git a/crates/workspace/src/panels/contact_list.rs b/crates/workspace/src/panels/contact_list.rs index b6afb557..f046a1b1 100644 --- a/crates/workspace/src/panels/contact_list.rs +++ b/crates/workspace/src/panels/contact_list.rs @@ -108,8 +108,10 @@ impl ContactListPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { let public_keys = task.await?; - // Update state - this.update(cx, |this, cx| { + // Update state. `update_in` (rather than `update`) routes through + // a try-borrow, so on wasm a poll that happens to land while the + // app context is borrowed can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.contacts.extend(public_keys); cx.notify(); })?; @@ -148,8 +150,11 @@ impl ContactListPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { cx.background_executor().timer(Duration::from_secs(2)).await; - // Clear the error message after a delay - this.update(cx, |this, cx| { + // Clear the error message after a delay. `update_in` (rather than + // `update`) routes through a try-borrow, so on wasm a poll that + // happens to land while the app context is borrowed can't panic + // and kill this task. + this.update_in(cx, |this, _window, cx| { this.error = None; cx.notify(); })?; diff --git a/crates/workspace/src/panels/messaging_relays.rs b/crates/workspace/src/panels/messaging_relays.rs index 93cde120..b933a4ab 100644 --- a/crates/workspace/src/panels/messaging_relays.rs +++ b/crates/workspace/src/panels/messaging_relays.rs @@ -103,8 +103,10 @@ impl MessagingRelayPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { let relays = task.await?; - // Update state - this.update(cx, |this, cx| { + // Update state. `update_in` (rather than `update`) routes through + // a try-borrow: on wasm a poll that lands while the app context + // is borrowed can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.relays.extend(relays); cx.notify(); })?; @@ -148,8 +150,11 @@ impl MessagingRelayPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { cx.background_executor().timer(Duration::from_secs(2)).await; - // Clear the error message after a delay - this.update(cx, |this, cx| { + // Clear the error message after a delay. `update_in` (rather than + // `update`) routes through a try-borrow: on wasm a poll that + // lands while the app context is borrowed can't panic and kill + // this task. + this.update_in(cx, |this, _window, cx| { this.error = None; cx.notify(); })?; diff --git a/crates/workspace/src/panels/profile.rs b/crates/workspace/src/panels/profile.rs index a944c9cf..581374e0 100644 --- a/crates/workspace/src/panels/profile.rs +++ b/crates/workspace/src/panels/profile.rs @@ -167,7 +167,7 @@ impl ProfilePanel { }); self.tasks.push(cx.spawn_in(window, async move |this, cx| { - this.update(cx, |this, cx| { + this.update_in(cx, |this, _window, cx| { this.set_uploading(true, cx); })?; diff --git a/crates/workspace/src/panels/relay_list.rs b/crates/workspace/src/panels/relay_list.rs index 4e76bd4f..45fefee8 100644 --- a/crates/workspace/src/panels/relay_list.rs +++ b/crates/workspace/src/panels/relay_list.rs @@ -121,8 +121,10 @@ impl RelayListPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { let relays = task.await?; - // Update state - this.update(cx, |this, cx| { + // Update state. `update_in` (rather than `update`) routes through + // a try-borrow: on wasm a poll that lands while the app context + // is borrowed can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.relays.extend(relays); cx.notify(); })?; @@ -167,8 +169,11 @@ impl RelayListPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { cx.background_executor().timer(Duration::from_secs(2)).await; - // Clear the error message after a delay - this.update(cx, |this, cx| { + // Clear the error message after a delay. `update_in` (rather than + // `update`) routes through a try-borrow: on wasm a poll that + // lands while the app context is borrowed can't panic and kill + // this task. + this.update_in(cx, |this, _window, cx| { this.error = None; cx.notify(); })?; diff --git a/crates/workspace/src/sidebar/mod.rs b/crates/workspace/src/sidebar/mod.rs index efb74af9..327ebb24 100644 --- a/crates/workspace/src/sidebar/mod.rs +++ b/crates/workspace/src/sidebar/mod.rs @@ -178,7 +178,10 @@ impl Sidebar { self.tasks.push(cx.spawn_in(window, async move |this, cx| { match task.await { Ok(contacts) => { - this.update(cx, |this, cx| { + // `update_in` (rather than `update`) routes through a + // try-borrow: on wasm a poll that lands while the app + // context is borrowed can't panic and kill this task. + this.update_in(cx, |this, _window, cx| { this.set_contact_list(contacts, cx); })?; }