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:
GenSpark AI Developer
2026-06-15 15:03:40 +00:00
parent 89126ef1c7
commit 42fc80bb55
2 changed files with 84 additions and 45 deletions

View File

@@ -709,54 +709,74 @@ 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
for wid in willexecutor["txsids"]: # verified afterwards (network I/O); collect them here and process
self.bal_window.willitems[wid].set_status( # them sequentially after the parallel push, keeping the original
"PUSH_FAIL", True # check logic untouched.
) already_present = []
retry = True retry_flag = {"value": False}
else:
for wid in willexecutor["txsids"]: def on_each(url, willexecutor, ok, exc):
self.bal_window.willitems[wid].set_status( # Runs from a worker thread. Do only thread-safe book-keeping
"PUSHED", True # plus a signal-based UI update (msg_edit_row emits a pyqtSignal,
) # which is marshalled to the GUI thread).
except Willexecutors.AlreadyPresentException: if isinstance(exc, Willexecutors.AlreadyPresentException):
already_present.append(url)
elif ok:
for wid in willexecutor["txsids"]: for wid in willexecutor["txsids"]:
if self._stopping: self.bal_window.willitems[wid].set_status("PUSHED", True)
return else:
row = self.msg_edit_row( for wid in willexecutor["txsids"]:
"checking {} - {} : {}".format( self.bal_window.willitems[wid].set_status("PUSH_FAIL", True)
self.bal_window.willitems[wid].we["url"], wid, "Waiting" retry_flag["value"] = True
) self.msg_edit_row(
) "{} : {}".format(url, "Ok" if ok else "Ko")
self.bal_plugin = self.bal_window.bal_plugin )
w = self.bal_window.willitems[wid]
w.set_check_willexecutor( if self._stopping:
Willexecutors.check_transaction(wid, w.we["url"]) return
) # Push to all selected will-executors in parallel: a slow/dead
row = self.msg_edit_row( # server no longer blocks the others, so the wizard's "Broadcasting"
"checked {} - {} : {}".format( # step is no longer sequential. Each server still keeps its own
self.bal_window.willitems[wid].we["url"], # retry behaviour inside push_transactions_to_willexecutor.
wid, Willexecutors.push_transactions_parallel(selected, on_each=on_each)
self.bal_window.willitems[wid].get_status("CHECKED"),
), retry = retry_flag["value"]
row,
) # 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:
return
row = self.msg_edit_row(
"checking {} - {} : {}".format(
self.bal_window.willitems[wid].we["url"], wid, "Waiting"
)
)
w = self.bal_window.willitems[wid]
w.set_check_willexecutor(
Willexecutors.check_transaction(wid, w.we["url"])
)
row = self.msg_edit_row(
"checked {} - {} : {}".format(
self.bal_window.willitems[wid].we["url"],
wid,
self.bal_window.willitems[wid].get_status("CHECKED"),
),
row,
)
except Exception as e:
_logger.error(f"loop push error:{e}")
raise e
if retry: if retry:
raise Exception("retry") raise Exception("retry")

View File

@@ -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