forked from bitcoinafterlife/bal-electrum-plugin
perf(wizard): parallelize Will-Executor broadcast in Building Will wizard
The Building Will wizard (BalBuildWillDialog.loop_push) still broadcast the will to will-executors sequentially -- a for-loop calling push_transactions_to_willexecutor one server at a time. This is the slow "Broadcasting your will to executors: Trasmissione" step the user saw: a slow/dead server blocked the whole wizard, just like the non-wizard path did before it was parallelized. Rewrite loop_push to use Willexecutors.push_transactions_parallel (the same helper already used by window.push_transactions_to_willexecutors): - Pre-filter to the user-selected will-executors only. - Push to all selected servers concurrently (ThreadPoolExecutor); each server keeps its own retry behaviour, but a slow/dead server no longer blocks the others. Total time ~= slowest server, not the sum. - on_each callback does thread-safe book-keeping + UI update via msg_edit_row (which emits a pyqtSignal marshalled to the GUI thread). - 'already present' servers are collected and their stored tx verified sequentially afterwards (original check_transaction logic preserved). - Preserve the retry flag and the _stopping cancellation checks. tests/parallel_ping_test.py: add a static check asserting loop_push uses push_transactions_parallel and no longer contains the sequential push loop. Tests: 182 official + smoke/overflow/gui_fixes/parallel/external_zip all pass. ruff: no new issues; new code is PEP8-compliant.
This commit is contained in:
@@ -709,29 +709,54 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
willexecutors = Willexecutors.get_willexecutor_transactions(
|
willexecutors = Willexecutors.get_willexecutor_transactions(
|
||||||
self.bal_window.willitems
|
self.bal_window.willitems
|
||||||
)
|
)
|
||||||
for url, willexecutor in willexecutors.items():
|
|
||||||
if self._stopping:
|
# Only push to the will-executors the user actually selected. We
|
||||||
return
|
# filter the mapping up-front so push_transactions_parallel only
|
||||||
try:
|
# talks to the relevant servers.
|
||||||
if Willexecutors.is_selected(
|
selected = {
|
||||||
self.bal_window.willexecutors.get(url)
|
url: we
|
||||||
):
|
for url, we in willexecutors.items()
|
||||||
_logger.debug(f"{url}: {willexecutor}")
|
if Willexecutors.is_selected(self.bal_window.willexecutors.get(url))
|
||||||
if not Willexecutors.push_transactions_to_willexecutor(
|
}
|
||||||
willexecutor
|
|
||||||
):
|
# Servers that report "already present" need their stored tx
|
||||||
|
# verified afterwards (network I/O); collect them here and process
|
||||||
|
# them sequentially after the parallel push, keeping the original
|
||||||
|
# check logic untouched.
|
||||||
|
already_present = []
|
||||||
|
retry_flag = {"value": False}
|
||||||
|
|
||||||
|
def on_each(url, willexecutor, ok, exc):
|
||||||
|
# Runs from a worker thread. Do only thread-safe book-keeping
|
||||||
|
# plus a signal-based UI update (msg_edit_row emits a pyqtSignal,
|
||||||
|
# which is marshalled to the GUI thread).
|
||||||
|
if isinstance(exc, Willexecutors.AlreadyPresentException):
|
||||||
|
already_present.append(url)
|
||||||
|
elif ok:
|
||||||
for wid in willexecutor["txsids"]:
|
for wid in willexecutor["txsids"]:
|
||||||
self.bal_window.willitems[wid].set_status(
|
self.bal_window.willitems[wid].set_status("PUSHED", True)
|
||||||
"PUSH_FAIL", True
|
|
||||||
)
|
|
||||||
retry = True
|
|
||||||
else:
|
else:
|
||||||
for wid in willexecutor["txsids"]:
|
for wid in willexecutor["txsids"]:
|
||||||
self.bal_window.willitems[wid].set_status(
|
self.bal_window.willitems[wid].set_status("PUSH_FAIL", True)
|
||||||
"PUSHED", True
|
retry_flag["value"] = True
|
||||||
|
self.msg_edit_row(
|
||||||
|
"{} : {}".format(url, "Ok" if ok else "Ko")
|
||||||
)
|
)
|
||||||
except Willexecutors.AlreadyPresentException:
|
|
||||||
for wid in willexecutor["txsids"]:
|
if self._stopping:
|
||||||
|
return
|
||||||
|
# Push to all selected will-executors in parallel: a slow/dead
|
||||||
|
# server no longer blocks the others, so the wizard's "Broadcasting"
|
||||||
|
# step is no longer sequential. Each server still keeps its own
|
||||||
|
# retry behaviour inside push_transactions_to_willexecutor.
|
||||||
|
Willexecutors.push_transactions_parallel(selected, on_each=on_each)
|
||||||
|
|
||||||
|
retry = retry_flag["value"]
|
||||||
|
|
||||||
|
# Verify the "already present" servers (sequential, original logic).
|
||||||
|
self.bal_plugin = self.bal_window.bal_plugin
|
||||||
|
for url in already_present:
|
||||||
|
for wid in willexecutors[url]["txsids"]:
|
||||||
if self._stopping:
|
if self._stopping:
|
||||||
return
|
return
|
||||||
row = self.msg_edit_row(
|
row = self.msg_edit_row(
|
||||||
@@ -739,9 +764,7 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
|
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.bal_plugin = self.bal_window.bal_plugin
|
|
||||||
w = self.bal_window.willitems[wid]
|
w = self.bal_window.willitems[wid]
|
||||||
|
|
||||||
w.set_check_willexecutor(
|
w.set_check_willexecutor(
|
||||||
Willexecutors.check_transaction(wid, w.we["url"])
|
Willexecutors.check_transaction(wid, w.we["url"])
|
||||||
)
|
)
|
||||||
@@ -754,9 +777,6 @@ class BalBuildWillDialog(BalDialog):
|
|||||||
row,
|
row,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
_logger.error(f"loop push error:{e}")
|
|
||||||
raise e
|
|
||||||
if retry:
|
if retry:
|
||||||
raise Exception("retry")
|
raise Exception("retry")
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,25 @@ def main():
|
|||||||
finally:
|
finally:
|
||||||
W.push_transactions_to_willexecutor = orig_push
|
W.push_transactions_to_willexecutor = orig_push
|
||||||
|
|
||||||
|
# ---- 3) the wizard's loop_push must use the parallel helper ----
|
||||||
|
# The "Building Will" wizard broadcasts via BalBuildWillDialog.loop_push.
|
||||||
|
# It previously looped over servers sequentially (one
|
||||||
|
# push_transactions_to_willexecutor call at a time), which is exactly the
|
||||||
|
# slow path the user saw at "Broadcasting your will to executors". Make
|
||||||
|
# sure it now delegates to push_transactions_parallel.
|
||||||
|
import inspect
|
||||||
|
dialogs_mod = importlib.import_module(f"{PKG}.gui.qt.dialogs")
|
||||||
|
loop_push_src = inspect.getsource(dialogs_mod.BalBuildWillDialog.loop_push)
|
||||||
|
code = "\n".join(
|
||||||
|
line for line in loop_push_src.splitlines()
|
||||||
|
if not line.lstrip().startswith("#")
|
||||||
|
)
|
||||||
|
assert "push_transactions_parallel" in code, (
|
||||||
|
"wizard loop_push must use push_transactions_parallel (parallel push)")
|
||||||
|
assert "for url, willexecutor in willexecutors.items()" not in code, (
|
||||||
|
"wizard loop_push must not push to servers in a sequential loop")
|
||||||
|
print("[OK] wizard loop_push uses push_transactions_parallel (not sequential)")
|
||||||
|
|
||||||
print(f"\n[OK] parallel networking test passed for package {PKG!r}")
|
print(f"\n[OK] parallel networking test passed for package {PKG!r}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user