fix: large-wallet sync starvation + shutdown/console-flash UX (Windows full node) #2

Open
DanS wants to merge 12 commits from fix/balance-poll-sync-contention into dev
2 changed files with 119 additions and 1 deletions
Showing only changes of commit 7e8b99a82b - Show all commits

View File

@@ -2224,6 +2224,7 @@ void App::render()
renderDecryptWalletDialog(); renderDecryptWalletDialog();
renderPinDialogs(); renderPinDialogs();
renderSwitchStopDaemonDialog(); renderSwitchStopDaemonDialog();
renderDaemonStopConfirm();
renderBlockDbReindexDialog(); renderBlockDbReindexDialog();
renderWalletRecoveredDialog(); renderWalletRecoveredDialog();
renderEmptyWalletWarningDialog(); renderEmptyWalletWarningDialog();
@@ -5652,6 +5653,16 @@ void App::beginShutdown()
{ {
// Only start shutdown once // Only start shutdown once
if (shutting_down_) return; if (shutting_down_) return;
// Guard: don't silently discard an in-progress witness-cache rebuild. If we're about to stop the
// daemon while it's rebuilding (stopping now forces a multi-minute rebuild on the next launch),
// defer shutdown and let render() show the confirm modal. The user's choice re-enters beginShutdown()
// with shutdown_confirmed_ set (and, for "keep node running", shutdown_keep_daemon_override_).
if (!shutdown_confirmed_ && shouldConfirmDaemonStop()) {
pending_shutdown_confirm_ = true;
return; // NOT shutting down yet — the normal UI + modal keep rendering
}
shutting_down_ = true; shutting_down_ = true;
quit_requested_ = true; quit_requested_ = true;
shutdown_timer_ = 0.0f; shutdown_timer_ = 0.0f;
@@ -5712,7 +5723,7 @@ void App::beginShutdown()
} }
auto shutdownDecision = daemon_controller_->shutdownDecision( auto shutdownDecision = daemon_controller_->shutdownDecision(
settings_ && settings_->getKeepDaemonRunning(), (settings_ && settings_->getKeepDaemonRunning()) || shutdown_keep_daemon_override_,
settings_ && settings_->getStopExternalDaemon()); settings_ && settings_->getStopExternalDaemon());
if (shutdownDecision.action == daemon::DaemonController::ShutdownAction::DisconnectOnly) { if (shutdownDecision.action == daemon::DaemonController::ShutdownAction::DisconnectOnly) {
DEBUG_LOGF("beginShutdown: %s, skipping daemon stop\n", shutdownDecision.logReason); DEBUG_LOGF("beginShutdown: %s, skipping daemon stop\n", shutdownDecision.logReason);
@@ -5775,6 +5786,100 @@ std::vector<std::string> App::tailDaemonDebugLog(int maxLines) const
return lines; return lines;
} }
bool App::daemonWitnessRebuildActive() const
{
// Scan the debug.log tail for the daemon's witness-rebuild markers (wallet.cpp): "Cleared witness
// data from" (start), "Setting Initial Sapling Witness" / "Reading blocks for witness rebuild"
// (progress), vs. "rebuilt N note witness cache(s)" / "aborting…" (finished). Active iff the most
// recent relevant line is a start/progress line, not a completion.
const auto lines = tailDaemonDebugLog(80);
int state = 0; // 0 none, 1 active, 2 finished/aborted
for (const auto& l : lines) {
if (l.find("note witness cache(s) to height") != std::string::npos ||
l.find("aborting witness rebuild") != std::string::npos ||
l.find("aborted during witness rebuild") != std::string::npos) {
state = 2;
} else if (l.find("Reading blocks for witness rebuild") != std::string::npos ||
l.find("Setting Initial Sapling Witness") != std::string::npos ||
l.find("Cleared witness data from") != std::string::npos) {
state = 1;
}
}
return state == 1;
}
bool App::shouldConfirmDaemonStop() const
{
if (!daemon_controller_) return false;
// Only relevant when this shutdown would actually STOP the daemon (embedded, or external with
// stop-on-exit) — a DisconnectOnly shutdown leaves it running and loses nothing.
const auto decision = daemon_controller_->shutdownDecision(
settings_ && settings_->getKeepDaemonRunning(),
settings_ && settings_->getStopExternalDaemon());
if (decision.action != daemon::DaemonController::ShutdownAction::StopDaemon) return false;
return daemonWitnessRebuildActive();
}
void App::renderDaemonStopConfirm()
{
using namespace ui::material;
if (pending_shutdown_confirm_) {
ImGui::OpenPopup("##DaemonStopConfirm");
pending_shutdown_confirm_ = false;
daemon_stop_confirm_open_ = true;
}
if (!daemon_stop_confirm_open_) return;
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
bool proceed = false;
if (ImGui::BeginPopupModal("##DaemonStopConfirm", nullptr,
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar)) {
if (Type().subtitle1()) ImGui::PushFont(Type().subtitle1());
ImGui::TextUnformatted("Node is rebuilding its witness cache");
if (Type().subtitle1()) ImGui::PopFont();
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 26.0f);
ImGui::TextUnformatted(
"Stopping the node now discards the in-progress rebuild and restarts it (several minutes) "
"the next time you open the wallet. You can keep the node running instead.");
ImGui::PopTextWrapPos();
ImGui::Spacing();
ImGui::Spacing();
if (TactileButton("Keep node running & quit", ImVec2(0, 0))) {
shutdown_keep_daemon_override_ = true;
shutdown_confirmed_ = true;
daemon_stop_confirm_open_ = false;
proceed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::ColorConvertU32ToFloat4(WithAlpha(Error(), 210)));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(Error()));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::ColorConvertU32ToFloat4(WithAlpha(Error(), 160)));
const bool stopAnyway = TactileButton("Stop anyway & quit", ImVec2(0, 0));
ImGui::PopStyleColor(3);
if (stopAnyway) {
shutdown_confirmed_ = true;
daemon_stop_confirm_open_ = false;
proceed = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (TactileButton("Cancel", ImVec2(0, 0))) {
daemon_stop_confirm_open_ = false; // abort the quit; stay open
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} else {
daemon_stop_confirm_open_ = false; // dismissed via Esc / click-away = Cancel
}
// Re-enter shutdown outside the popup scope now that the user has chosen (shutdown_confirmed_ set).
if (proceed) beginShutdown();
}
void App::renderShutdownScreen() void App::renderShutdownScreen()
{ {
using namespace ui::material; using namespace ui::material;

View File

@@ -182,6 +182,14 @@ public:
*/ */
std::vector<std::string> tailDaemonDebugLog(int maxLines) const; std::vector<std::string> tailDaemonDebugLog(int maxLines) const;
// True when the daemon's debug.log shows an in-progress Sapling witness-cache rebuild (best-effort
// heuristic). Stopping the daemon during one discards it and forces a multi-minute redo next launch.
bool daemonWitnessRebuildActive() const;
// Whether beginShutdown() should pause and confirm before stopping the daemon (rebuild in progress).
bool shouldConfirmDaemonStop() const;
// The "node is rebuilding — stop anyway / keep running / cancel" modal, rendered from render().
void renderDaemonStopConfirm();
/** /**
* @brief Render loading overlay in content area while daemon is starting/syncing * @brief Render loading overlay in content area while daemon is starting/syncing
* @param contentH Height of the content area child window * @param contentH Height of the content area child window
@@ -911,6 +919,11 @@ private:
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
GuardedStatus shutdown_status_; // thread-safe: written by the shutdown thread, read by the UI (M-05) GuardedStatus shutdown_status_; // thread-safe: written by the shutdown thread, read by the UI (M-05)
std::thread shutdown_thread_; std::thread shutdown_thread_;
// Confirm-before-stopping-daemon-mid-witness-rebuild guard (see beginShutdown / renderDaemonStopConfirm)
bool pending_shutdown_confirm_ = false; // a quit is deferred, waiting to open the confirm modal
bool daemon_stop_confirm_open_ = false; // the confirm modal is currently showing
bool shutdown_confirmed_ = false; // user chose to proceed — bypass the guard on re-entry
bool shutdown_keep_daemon_override_ = false; // user chose "keep node running" for this shutdown only
float shutdown_timer_ = 0.0f; float shutdown_timer_ = 0.0f;
bool force_quit_confirm_ = false; bool force_quit_confirm_ = false;
std::chrono::steady_clock::time_point shutdown_start_time_; std::chrono::steady_clock::time_point shutdown_start_time_;