From 8cc3b58364efef6c671a5b53161ac73e3657114e Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Nov 2015 08:40:58 +0900 Subject: [PATCH 1/5] Allow syncronizer to be GC-ed Proper fix for #1525. Using python's GC module, I've verified that the daemon, when running, now releases all verifiers, synchronizers and wallets - all the resources we care about releasing. --- lib/network.py | 7 +++++++ lib/synchronizer.py | 3 +++ lib/wallet.py | 5 ++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/network.py b/lib/network.py index 31201ad9..2054b856 100644 --- a/lib/network.py +++ b/lib/network.py @@ -556,6 +556,13 @@ class Network(util.DaemonThread): message_id = self.queue_request(method, params) self.unanswered_requests[message_id] = method, params, callback + def unsubscribe(self, callback): + '''Unsubscribe a callback to free object references to enable GC.''' + # Note: we can't unsubscribe from the server, so if we receive + # subsequent notifications process_response() will emit a harmless + # "received unexpected notification" warning + self.subscriptions.pop(callback, None) + def connection_down(self, server): '''A connection to server either went down, or was never made. We distinguish by whether it is in self.interfaces.''' diff --git a/lib/synchronizer.py b/lib/synchronizer.py index ef4796b0..9b512671 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -56,6 +56,9 @@ class Synchronizer(ThreadJob): return (not self.requested_tx and not self.requested_histories and not self.requested_addrs) + def release(self): + self.network.unsubscribe(self.addr_subscription_response) + def add(self, address): '''This can be called from the proxy or GUI threads.''' with self.lock: diff --git a/lib/wallet.py b/lib/wallet.py index 8a6fcfd1..7cbc81a0 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -36,6 +36,7 @@ from transaction import Transaction from plugins import run_hook import bitcoin from synchronizer import Synchronizer +from verifier import SPV from mnemonic import Mnemonic import paymentrequest @@ -1128,7 +1129,6 @@ class Abstract_Wallet(PrintError): self.transactions.pop(tx_hash) def start_threads(self, network): - from verifier import SPV self.network = network if self.network is not None: self.prepare_for_verifier() @@ -1142,8 +1142,11 @@ class Abstract_Wallet(PrintError): def stop_threads(self): if self.network: self.network.remove_jobs([self.synchronizer, self.verifier]) + self.synchronizer.release() self.synchronizer = None self.verifier = None + # Now no references to the syncronizer or verifier + # remain so they will be GC-ed self.storage.put('stored_height', self.get_local_height(), True) def wait_until_synchronized(self, callback=None): From d1cd6d5645e5f9d41f26963268b3ef602652895f Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Nov 2015 11:29:53 +0900 Subject: [PATCH 2/5] Labels plugin: drop wallet on window close Will help GC --- plugins/labels.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/labels.py b/plugins/labels.py index f9598f20..7c74dfdf 100644 --- a/plugins/labels.py +++ b/plugins/labels.py @@ -53,6 +53,9 @@ class Plugin(BasePlugin): t.setDaemon(True) t.start() + def on_close_window(self, window): + self.wallets.pop(window.wallet) + def version(self): return "0.0.1" From afb50132725449825ad5cb9c572e14a962b3ec04 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Nov 2015 15:32:24 +0900 Subject: [PATCH 3/5] Remove non-existent callbacks and handlers --- gui/android.py | 3 --- gui/stdio.py | 13 +------------ gui/text.py | 3 --- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/gui/android.py b/gui/android.py index c1d9f472..16d14d8a 100644 --- a/gui/android.py +++ b/gui/android.py @@ -909,9 +909,6 @@ class ElectrumGui: network = _network config = _config network.register_callback('updated', update_callback) - network.register_callback('connected', update_callback) - network.register_callback('disconnected', update_callback) - network.register_callback('disconnecting', update_callback) contacts = util.StoreDict(config, 'contacts') diff --git a/gui/stdio.py b/gui/stdio.py index e338ba96..8688690d 100644 --- a/gui/stdio.py +++ b/gui/stdio.py @@ -35,9 +35,7 @@ class ElectrumGui: self.contacts = StoreDict(self.config, 'contacts') self.wallet.network.register_callback('updated', self.updated) - self.wallet.network.register_callback('connected', self.connected) - self.wallet.network.register_callback('disconnected', self.disconnected) - self.wallet.network.register_callback('disconnecting', self.disconnecting) + self.wallet.network.register_callback('peers', self.peers) self.wallet.network.register_callback('banner', self.print_banner) self.commands = [_("[h] - displays this help text"), \ @@ -73,15 +71,6 @@ class ElectrumGui: for s in l: print (s) - def connected(self): - print ("connected") - - def disconnected(self): - print ("disconnected") - - def disconnecting(self): - print ("disconnecting") - def updated(self): s = self.get_balance() if s != self.last_balance: diff --git a/gui/text.py b/gui/text.py index bae39af3..5d1e2421 100644 --- a/gui/text.py +++ b/gui/text.py @@ -54,9 +54,6 @@ class ElectrumGui: if self.network: self.network.register_callback('updated', self.update) - self.network.register_callback('connected', self.refresh) - self.network.register_callback('disconnected', self.refresh) - self.network.register_callback('disconnecting', self.refresh) self.tab_names = [_("History"), _("Send"), _("Receive"), _("Addresses"), _("Contacts"), _("Banner")] self.num_tabs = len(self.tab_names) From 51ebdc5ce8f7e6c4c747a42d2e0d433a617fe248 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Nov 2015 15:33:58 +0900 Subject: [PATCH 4/5] Remove another non-existent callback --- gui/qt/main_window.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 9affe266..0641c981 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -163,7 +163,6 @@ class ElectrumWindow(QMainWindow, PrintError): self.network.register_callback('updated', lambda: self.need_update.set()) self.network.register_callback('new_transaction', self.new_transaction) self.register_callback('status', self.update_status) - self.register_callback('close', self.close) self.register_callback('banner', self.console.showMessage) self.register_callback('verified', self.history_list.update_item) From 206e38fb7c50323b0194e269c614255195959b6a Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Thu, 12 Nov 2015 16:08:37 +0900 Subject: [PATCH 5/5] Make use of trigger_callback easier to understand --- lib/network.py | 7 +++---- lib/synchronizer.py | 2 +- lib/wallet.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/network.py b/lib/network.py index 2054b856..43352e18 100644 --- a/lib/network.py +++ b/lib/network.py @@ -198,10 +198,10 @@ class Network(util.DaemonThread): with self.lock: self.callbacks[event].append(callback) - def trigger_callback(self, event, params=()): + def trigger_callback(self, event, *args): with self.lock: callbacks = self.callbacks[event][:] - [callback(*params) for callback in callbacks] + [callback(*args) for callback in callbacks] def read_recent_servers(self): if not self.config.path: @@ -294,11 +294,10 @@ class Network(util.DaemonThread): return value def notify(self, key): - value = self.get_status_value(key) if key in ['status', 'updated']: self.trigger_callback(key) else: - self.trigger_callback(key, (value,)) + self.trigger_callback(key, self.get_status_value(key)) def get_parameters(self): host, port, protocol = deserialize_server(self.default_server) diff --git a/lib/synchronizer.py b/lib/synchronizer.py index 9b512671..8762ea0d 100644 --- a/lib/synchronizer.py +++ b/lib/synchronizer.py @@ -129,7 +129,7 @@ class Synchronizer(ThreadJob): self.print_error("received tx %s height: %d bytes: %d" % (tx_hash, tx_height, len(tx.raw))) # callbacks - self.network.trigger_callback('new_transaction', (tx,)) + self.network.trigger_callback('new_transaction', tx) if not self.requested_tx: self.network.trigger_callback('updated') diff --git a/lib/wallet.py b/lib/wallet.py index 7cbc81a0..80c20897 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -435,7 +435,7 @@ class Abstract_Wallet(PrintError): self.storage.put('verified_tx3', self.verified_tx, True) conf, timestamp = self.get_confirmations(tx_hash) - self.network.trigger_callback('verified', (tx_hash, conf, timestamp)) + self.network.trigger_callback('verified', tx_hash, conf, timestamp) def get_unverified_txs(self): '''Returns a map from tx hash to transaction height'''