fix: large-wallet sync starvation + shutdown/console-flash UX (Windows full node) #2
107
src/app.cpp
107
src/app.cpp
@@ -2224,6 +2224,7 @@ void App::render()
|
||||
renderDecryptWalletDialog();
|
||||
renderPinDialogs();
|
||||
renderSwitchStopDaemonDialog();
|
||||
renderDaemonStopConfirm();
|
||||
renderBlockDbReindexDialog();
|
||||
renderWalletRecoveredDialog();
|
||||
renderEmptyWalletWarningDialog();
|
||||
@@ -5652,6 +5653,16 @@ void App::beginShutdown()
|
||||
{
|
||||
// Only start shutdown once
|
||||
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;
|
||||
quit_requested_ = true;
|
||||
shutdown_timer_ = 0.0f;
|
||||
@@ -5712,7 +5723,7 @@ void App::beginShutdown()
|
||||
}
|
||||
|
||||
auto shutdownDecision = daemon_controller_->shutdownDecision(
|
||||
settings_ && settings_->getKeepDaemonRunning(),
|
||||
(settings_ && settings_->getKeepDaemonRunning()) || shutdown_keep_daemon_override_,
|
||||
settings_ && settings_->getStopExternalDaemon());
|
||||
if (shutdownDecision.action == daemon::DaemonController::ShutdownAction::DisconnectOnly) {
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
using namespace ui::material;
|
||||
|
||||
13
src/app.h
13
src/app.h
@@ -182,6 +182,14 @@ public:
|
||||
*/
|
||||
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
|
||||
* @param contentH Height of the content area child window
|
||||
@@ -911,6 +919,11 @@ private:
|
||||
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
|
||||
GuardedStatus shutdown_status_; // thread-safe: written by the shutdown thread, read by the UI (M-05)
|
||||
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;
|
||||
bool force_quit_confirm_ = false;
|
||||
std::chrono::steady_clock::time_point shutdown_start_time_;
|
||||
|
||||
Reference in New Issue
Block a user