From 2d6f6074c1dae85103a9e6c7b731c362befe945b Mon Sep 17 00:00:00 2001 From: copp1723 <200090596+copp1723@users.noreply.github.com> Date: Sun, 10 May 2026 21:42:53 -0500 Subject: [PATCH 1/4] fix monero wallet listener lifecycle --- monero-sys/src/lib.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/monero-sys/src/lib.rs b/monero-sys/src/lib.rs index ad76e94c1..144ee3ad9 100644 --- a/monero-sys/src/lib.rs +++ b/monero-sys/src/lib.rs @@ -17,7 +17,7 @@ pub use bridge::wallet_listener; pub use bridge::{TraceListener, WalletEventListener, WalletListenerBox}; pub use database::{Database, RecentWallet}; use std::panic::{AssertUnwindSafe, catch_unwind}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, Weak}; use std::{ any::Any, cmp::Ordering, collections::HashMap, fmt::Display, future::Future, ops::Deref, pin::Pin, time::Duration, @@ -3219,7 +3219,7 @@ impl Deref for TransactionInfoHandle { /// This listener does things on certain events like storing the wallet to disk. /// This is supposed to improve upon the behaviour of wallet2 pub struct WalletHandleListener { - wallet: Arc, + wallet: Weak, /// We need a handle to the runtime to be able to spawn tasks rt_handle: tokio::runtime::Handle, /// We throttle the saving of the wallet to disk to avoid storing the wallet too often @@ -3234,6 +3234,7 @@ impl WalletHandleListener { pub fn new(wallet: Arc) -> Self { // Get the current runtime handle let rt_handle = tokio::runtime::Handle::current(); + let wallet = Arc::downgrade(&wallet); // Create a throttle wrapper around the save job let store_job = { @@ -3241,7 +3242,10 @@ impl WalletHandleListener { let rt = rt_handle.clone(); move |()| { - let wallet = wallet.clone(); + let Some(wallet) = wallet.upgrade() else { + tracing::trace!("Skipping wallet store because wallet handle is gone"); + return; + }; let rt = rt.clone(); rt.spawn(async move { @@ -3291,7 +3295,10 @@ impl WalletEventListener for WalletHandleListener { // When the wallet finishes refreshing, we start the refresh thread again. // The purpose of this is to ensure that if the user does a rescan (restore height changed) // We start the refresh thread again after the rescan is complete. - let handle = self.wallet.clone(); + let Some(handle) = self.wallet.upgrade() else { + tracing::trace!("Skipping refresh thread restart because wallet handle is gone"); + return; + }; self.rt_handle.spawn(async move { if let Err(e) = handle.start_refresh_thread().await { tracing::error!(error=%e, "Failed to start refresh thread"); From 3101df25d4bb50b0981f479aa9c705375a352a73 Mon Sep 17 00:00:00 2001 From: copp1723 <200090596+copp1723@users.noreply.github.com> Date: Sun, 10 May 2026 21:43:00 -0500 Subject: [PATCH 2/4] fix tauri wallet listener lifecycle --- monero-wallet/src/listener.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/monero-wallet/src/listener.rs b/monero-wallet/src/listener.rs index 060f9ba3b..4714613f3 100644 --- a/monero-wallet/src/listener.rs +++ b/monero-wallet/src/listener.rs @@ -1,4 +1,7 @@ -use std::{sync::Arc, time::Duration}; +use std::{ + sync::{Arc, Weak}, + time::Duration, +}; use monero_sys::WalletEventListener; @@ -29,13 +32,17 @@ impl TauriWalletListener { pub async fn new(tauri_handle: TauriHandle, wallet: Arc) -> Self { let rt_handle = tokio::runtime::Handle::current(); + let wallet: Weak = Arc::downgrade(&wallet); let balance_job = { let wallet = wallet.clone(); let tauri = tauri_handle.clone(); let rt = rt_handle.clone(); move |()| { - let wallet = wallet.clone(); + let Some(wallet) = wallet.upgrade() else { + tracing::trace!("Skipping balance update because wallet handle is gone"); + return; + }; let tauri = tauri.clone(); let rt = rt.clone(); rt.spawn(async move { @@ -64,7 +71,10 @@ impl TauriWalletListener { let tauri = tauri_handle.clone(); let rt = rt_handle.clone(); move |()| { - let wallet = wallet.clone(); + let Some(wallet) = wallet.upgrade() else { + tracing::trace!("Skipping history update because wallet handle is gone"); + return; + }; let tauri = tauri.clone(); let rt = rt.clone(); rt.spawn(async move { @@ -86,7 +96,10 @@ impl TauriWalletListener { let tauri = tauri_handle.clone(); let rt = rt_handle.clone(); move |()| { - let wallet = wallet.clone(); + let Some(wallet) = wallet.upgrade() else { + tracing::trace!("Skipping sync update because wallet handle is gone"); + return; + }; let tauri = tauri.clone(); let rt = rt.clone(); rt.spawn(async move { From a1c849fe2636849825f1c107dee2326db46cd265 Mon Sep 17 00:00:00 2001 From: copp1723 <200090596+copp1723@users.noreply.github.com> Date: Sun, 10 May 2026 21:45:29 -0500 Subject: [PATCH 3/4] return error for closed wallet subaddress balance channel --- monero-sys/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monero-sys/src/lib.rs b/monero-sys/src/lib.rs index 144ee3ad9..8c974bd93 100644 --- a/monero-sys/src/lib.rs +++ b/monero-sys/src/lib.rs @@ -682,10 +682,12 @@ impl WalletHandle { /// Returns a map of subaddress index -> balance (in atomic units). /// strict: If true, only includes confirmed and unlocked balance. /// If false, pending and unconfirmed transactions are also included. - pub async fn balance_per_subaddress(&self) -> std::collections::HashMap { + pub async fn balance_per_subaddress( + &self, + ) -> anyhow::Result> { self.call(move |wallet| wallet.balance_per_subaddress_sync()) .await - .expect("wallet thread closed while fetching balance per subaddress") + .context("Couldn't complete wallet call") } /// Check if the wallet is synchronized. From 19602be1777067c0a7d03854a7db626cdf95b3dd Mon Sep 17 00:00:00 2001 From: copp1723 <200090596+copp1723@users.noreply.github.com> Date: Sun, 10 May 2026 21:45:34 -0500 Subject: [PATCH 4/4] propagate subaddress balance channel errors in harness --- monero-harness/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index 74963b910..498009d06 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -531,7 +531,7 @@ impl MoneroWallet { /// Get non-strict balance per subaddress for main account (index 0). pub async fn balance_per_subaddress(&self) -> Result> { - Ok(self.wallet.balance_per_subaddress().await) + self.wallet.balance_per_subaddress().await } pub async fn refresh(&self) -> Result<()> {