fix(lite): flush the wallet save on quit before _Exit
On quit the lite controller's destructor never runs, because main() ends the process with _Exit(). Any state the background refresh had scanned since the last save was lost: the destructor is where that final save would normally happen, and it was being skipped on the one path that always runs. - shutdown() now calls a new flushOnExit() that persists once on the way out, bounded to 4 s so the existing 15 s shutdown watchdog budget still holds. On timeout it abandons the save thread and lets _Exit follow. - It is a plain save through the same backend path, which writes a temp file and renames it into place. That cannot corrupt the wallet if it is cut off or abandoned, and it does not change the wallet file format - it only persists sooner on the quit path. - It reuses the destructor's guards and also skips when a wallet op is already in progress: shutdown() has already waited for ops to go idle, and a still-pending op holds the sync lock and issues its own save, so flushOnExit would only block the budget on a save that cannot take the lock. Tests cover a save on the open, synced, idle path (exactly once), a blocked save returning within the budget, and the retired and never-opened guards skipping the save. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10427,6 +10427,63 @@ void testLiteWalletControllerM5Persistence()
|
||||
}
|
||||
}
|
||||
|
||||
// C30: flushOnExit() persists once on the quit path, is bounded when the save blocks, and obeys the guards.
|
||||
void testLiteWalletControllerFlushOnExit()
|
||||
{
|
||||
using namespace dragonx::wallet;
|
||||
const auto liteCaps = makeWalletCapabilities(WalletBuildKind::Lite, false, true);
|
||||
const LiteConnectionSettings conn = defaultLiteConnectionSettings();
|
||||
auto openSynced = [&]() {
|
||||
dragonx::test::g_liteFakeSyncBlock = false;
|
||||
auto c = std::make_unique<LiteWalletController>(
|
||||
liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()));
|
||||
LiteWalletCreateRequest req;
|
||||
(void)c->createWallet(req);
|
||||
for (int i = 0; i < 400 && !c->syncComplete(); ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
return c;
|
||||
};
|
||||
|
||||
// (a) open + synced + idle -> exactly one save
|
||||
{
|
||||
auto c = openSynced();
|
||||
EXPECT_TRUE(c->syncComplete());
|
||||
const long before = dragonx::test::g_liteFakeSaveCount.load();
|
||||
c->flushOnExit(std::chrono::milliseconds(500));
|
||||
EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before + 1);
|
||||
}
|
||||
|
||||
// (b) a blocked save never hangs the caller past the budget
|
||||
{
|
||||
auto c = openSynced();
|
||||
dragonx::test::g_liteFakeBlockSave = true;
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
c->flushOnExit(std::chrono::milliseconds(200));
|
||||
const auto dt = std::chrono::steady_clock::now() - t0;
|
||||
EXPECT_TRUE(dt >= std::chrono::milliseconds(150));
|
||||
EXPECT_TRUE(dt < std::chrono::milliseconds(1500));
|
||||
dragonx::test::g_liteFakeBlockSave = false; // let the abandoned save thread finish
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
||||
}
|
||||
|
||||
// (c) retired -> no save
|
||||
{
|
||||
auto c = openSynced();
|
||||
c->retire("switch");
|
||||
const long before = dragonx::test::g_liteFakeSaveCount.load();
|
||||
c->flushOnExit(std::chrono::milliseconds(300));
|
||||
EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before);
|
||||
}
|
||||
|
||||
// (d) wallet never opened -> no save
|
||||
{
|
||||
LiteWalletController c(liteCaps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi()));
|
||||
const long before = dragonx::test::g_liteFakeSaveCount.load();
|
||||
c.flushOnExit(std::chrono::milliseconds(300));
|
||||
EXPECT_EQ(dragonx::test::g_liteFakeSaveCount.load(), before);
|
||||
}
|
||||
}
|
||||
|
||||
// Encryption: the controller drives encrypt/unlock/lock/decrypt + encryptionstatus through the
|
||||
// (injected fake) backend, wipes passphrases, and folds status into WalletState (encrypted/locked).
|
||||
void testLiteWalletControllerEncryption()
|
||||
@@ -18912,6 +18969,7 @@ int main()
|
||||
testLiteRolloutPolicyStagedRollout();
|
||||
testLiteRolloutManifestLoader();
|
||||
testLiteWalletControllerRolloutGate();
|
||||
testLiteWalletControllerFlushOnExit();
|
||||
testXmrigReleaseParsing();
|
||||
testXmrigAssetSelection();
|
||||
testXmrigChecksumParsing();
|
||||
|
||||
Reference in New Issue
Block a user