release the node refresh subscription when a wallet is closed, and on a settings form that can never receive one
What changed, and why it matters
This change fixes a resource leak in the Sparrow Wallet desktop app. When a wallet tab was closed, a background subscription that refreshes wallet data from the connected server was not being stopped, so it could keep running and potentially cause memory to be held longer than needed. The patch now explicitly stops (disposes of) that subscription when a wallet is closed, and also prevents a settings form from ever starting one.
Review other RxJava subscriptions in WalletForm and related classes to ensure all Disposables are tracked and disposed when no longer needed. Consider using a CompositeDisposable for lifecycle management.
Security signals we found
Resource leak / memory retention from undisposed RxJava subscription
Background subscription continues after wallet UI is closed
Potential for stale wallet state or unexpected background activity after tab close
Evidence from the diff
WalletForm creates a PublishSubject (refreshNodesSubject) buffered into 1-second windows and observed on the JavaFX platform thread. Previously the resulting Disposable was not retained or disposed. The patch stores it as refreshNodesDisposable and adds disposeRefreshNodes() calls in walletTabsClosed() for both the parent WalletForm and its nested forms. SettingsWalletForm now calls disposeRefreshNodes() in its constructor because it inherits from WalletForm but should not receive refresh-node events. This prevents RxJava subscriptions from outliving closed wallet tabs.
Changed components
src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.javasrc/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.javaInspect captured patch +11 / −1
### src/main/java/com/sparrowwallet/sparrow/wallet/SettingsWalletForm.java
@@ -29,6 +29,7 @@ public SettingsWalletForm(Storage storage, Wallet currentWallet, WalletForm appW
this.walletCopy = currentWallet.copy();
this.walletCopy.setMasterWallet(walletCopy.isMasterWallet() ? null : walletCopy.getMasterWallet().copy());
this.appWalletForm = appWalletForm;
+ disposeRefreshNodes();
}
@Override
### src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java
@@ -16,6 +16,7 @@
import com.sparrowwallet.sparrow.net.AllHistoryChangedException;
import com.sparrowwallet.sparrow.net.ElectrumServer;
import com.sparrowwallet.sparrow.io.Storage;
+import io.reactivex.disposables.Disposable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.subjects.PublishSubject;
import javafx.application.Platform;
@@ -61,12 +62,14 @@ public class WalletForm {
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
+ private final Disposable refreshNodesDisposable;
+
public WalletForm(Storage storage, Wallet currentWallet) {
this.storage = storage;
this.wallet = currentWallet;
refreshNodesSubject = PublishSubject.create();
- refreshNodesSubject.buffer(1, TimeUnit.SECONDS)
+ refreshNodesDisposable = refreshNodesSubject.buffer(1, TimeUnit.SECONDS)
.filter(walletNodes -> !walletNodes.isEmpty())
.observeOn(JavaFxScheduler.platform())
.subscribe(walletNodes -> {
@@ -506,6 +509,10 @@ public List<NodeEntry> getAccountEntries() {
return accountEntries;
}
+ void disposeRefreshNodes() {
+ refreshNodesDisposable.dispose();
+ }
+
@Subscribe
public void silentPaymentsScanProgress(SilentPaymentsScanProgressEvent event) {
if(wallet.getPolicyType() != PolicyType.SINGLE_SP || !wallet.isValid() || !event.getSpAddress().equals(wallet.getSilentPaymentScanAddress().getAddress())) {
@@ -817,8 +824,10 @@ public void walletTabsClosed(WalletTabsClosedEvent event) {
for(WalletTabData tabData : event.getClosedWalletTabData()) {
if(tabData.getWalletForm() == this) {
EventManager.get().unregister(this);
+ disposeRefreshNodes();
for(WalletForm nestedWalletForm : nestedWalletForms) {
EventManager.get().unregister(nestedWalletForm);
+ nestedWalletForm.disposeRefreshNodes();
}
if(wallet.isValid()) {
AppServices.clearTransactionHistoryCache(wallet);Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.