fix(wallet): reliable process detection + drop the harmful start-retry
Two issues the latest live log exposed: - The switch's start-retry spawned a SECOND dragonxd while the first was still shutting down, so the two held wallet.dat against each other (BDB "Failed to rename wallet-savings.dat … Error"), and it then span on a stale "Daemon already running". Revert to a single start — the stopDaemonForWalletSwitch() wait already ensures the old process is gone, so a valid wallet opens cleanly and a bad one exits during init and reverts, without overlapping spawns. - findProcessByName() used the non-suffixed PROCESSENTRY32/Process32First with an ANSI _stricmp; if UNICODE is defined those map to the wide variants, so the compare comparing garbage would NEVER match — silently making the process-gone wait a no-op. Rewritten with the explicit wide Toolhelp API + lstrcmpiW so it's correct either way (verified to compile under mingw with and without -DUNICODE). Note: a corrupt wallet.dat (BDB recovery failing) still can't be opened by any node — that's a data issue needing a clean reset, not a switch-flow bug. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1137,21 +1137,18 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
|
|||||||
// as owned (stop/isRunning/exit behave normally for the next switch and app exit).
|
// as owned (stop/isRunning/exit behave normally for the next switch and app exit).
|
||||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Starting));
|
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Starting));
|
||||||
if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected();
|
if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected();
|
||||||
// Retry the start. Right after the old node exits, its datadir lock can linger for a few
|
if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true);
|
||||||
// seconds (the process is gone but the OS/BDB lock isn't released yet), so the first start
|
// Start ONCE. Do NOT retry-spawn: a second start while the first is still shutting down leaves
|
||||||
// hits "Cannot obtain a lock on data directory" and exits within the grace below. Retry —
|
// two dragonxd holding wallet.dat against each other (BDB "Failed to rename … Error"). The
|
||||||
// with the CORRECT -wallet, since active_wallet_file isn't reverted until we give up — until
|
// stopDaemonForWalletSwitch() wait already ensured the old node's process is gone, so a valid
|
||||||
// one survives (valid launch stays alive while it loads the block index/rescans, ~30-60s).
|
// wallet opens cleanly; a bad/corrupt wallet exits during init and we revert.
|
||||||
for (int attempt = 0; attempt < 6 && !ok && !shutting_down_; ++attempt) {
|
ok = shutting_down_ ? true : startEmbeddedDaemon();
|
||||||
if (attempt > 0) std::this_thread::sleep_for(std::chrono::milliseconds(2000)); // let the lock release
|
// A missing/corrupt wallet makes dragonxd exit during init — confirm the process survived a
|
||||||
if (daemon_controller_) daemon_controller_->resetCrashCount(); // retry lock-crashes aren't real
|
// moment; if not, the wallet is bad. (A valid launch keeps the process alive while it syncs.)
|
||||||
if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); // one-shot; re-arm each try
|
if (ok && !shutting_down_) {
|
||||||
if (shutting_down_) { ok = true; break; }
|
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||||
if (!startEmbeddedDaemon()) continue; // couldn't spawn (port busy) — back off + retry
|
if (!isEmbeddedDaemonRunning()) ok = false;
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1500)); // grace: a datadir-lock crash exits within this
|
|
||||||
ok = shutting_down_ || isEmbeddedDaemonRunning();
|
|
||||||
}
|
}
|
||||||
if (ok && daemon_controller_) daemon_controller_->resetCrashCount(); // clean slate for the survivor
|
|
||||||
// Process is up and staying up — now waiting for RPC to answer (onConnected closes the modal).
|
// Process is up and staying up — now waiting for RPC to answer (onConnected closes the modal).
|
||||||
if (ok) wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Reconnecting));
|
if (ok) wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Reconnecting));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -684,17 +684,24 @@ static DWORD findProcessByName(const char* name)
|
|||||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||||
if (snap == INVALID_HANDLE_VALUE) return 0;
|
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||||
|
|
||||||
PROCESSENTRY32 entry;
|
// Use the explicit WIDE Toolhelp API + a wide compare so this is correct regardless of the UNICODE
|
||||||
|
// macro. (The non-suffixed PROCESSENTRY32/Process32First map to the wide variants when UNICODE is
|
||||||
|
// defined, in which case szExeFile is WCHAR[] and an ANSI _stricmp would compare garbage and NEVER
|
||||||
|
// match — silently making findProcessByName a no-op that returns 0 for a running process.)
|
||||||
|
wchar_t wname[MAX_PATH];
|
||||||
|
if (MultiByteToWideChar(CP_ACP, 0, name, -1, wname, MAX_PATH) == 0) { CloseHandle(snap); return 0; }
|
||||||
|
|
||||||
|
PROCESSENTRY32W entry;
|
||||||
entry.dwSize = sizeof(entry);
|
entry.dwSize = sizeof(entry);
|
||||||
|
|
||||||
DWORD pid = 0;
|
DWORD pid = 0;
|
||||||
if (Process32First(snap, &entry)) {
|
if (Process32FirstW(snap, &entry)) {
|
||||||
do {
|
do {
|
||||||
if (_stricmp(entry.szExeFile, name) == 0) {
|
if (lstrcmpiW(entry.szExeFile, wname) == 0) { // Win32 case-insensitive wide compare
|
||||||
pid = entry.th32ProcessID;
|
pid = entry.th32ProcessID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while (Process32Next(snap, &entry));
|
} while (Process32NextW(snap, &entry));
|
||||||
}
|
}
|
||||||
CloseHandle(snap);
|
CloseHandle(snap);
|
||||||
return pid;
|
return pid;
|
||||||
|
|||||||
Reference in New Issue
Block a user