fix(lite): non-blocking, non-hanging sync (Finding B)
The backend `sync` command is a blocking, uninterruptible full chain scan (do_sync(true); does not honor the shutdown flag), and balance/list block until synced. Previously startSync() ran on the main thread (would freeze wallet creation) and the worker could block, making the destructor join() hang at shutdown. Redesign: - bridge is now std::shared_ptr<LiteClientBridge>, shared with a detached sync thread so detaching is safe and litelib_shutdown isn't called while a running sync still holds the bridge; the controller's own ref prevents premature shutdown during normal operation. - startSync() launches the blocking `sync` on a detached thread (non-blocking; never joined). - refreshModel() gates on syncDone_: while syncing it publishes syncstatus progress only; once synced it does the full balance/addresses/list refresh (now fast). - destructor joins only the fast poll worker and detaches the sync thread -> no hang. - syncComplete() accessor added. Tests (deterministic, via a blocking-sync fake; counters made atomic for the detached thread): testLiteWalletControllerShutdownDoesNotHangDuringSync (destructor returns <1.5s with sync blocked); refresh/worker tests wait for syncComplete()/a balance-bearing model. Stable across repeated runs; lite+backend and full-node apps build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,18 +17,22 @@
|
||||
|
||||
#include "wallet/lite_client_bridge.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
namespace dragonx {
|
||||
namespace test {
|
||||
|
||||
// Owned-string accounting (C++17 inline vars: single definition across TUs).
|
||||
inline long g_liteFakeAlloc = 0; // owned strings handed to the bridge
|
||||
inline long g_liteFakeFreed = 0; // owned strings released via freeString
|
||||
// Owned-string accounting (atomic: a detached sync thread may touch these concurrently).
|
||||
inline std::atomic<long> g_liteFakeAlloc{0}; // owned strings handed to the bridge
|
||||
inline std::atomic<long> g_liteFakeFreed{0}; // owned strings released via freeString
|
||||
inline bool g_liteFakeWalletExists = true;
|
||||
inline bool g_liteFakeServerOnline = true;
|
||||
inline bool g_liteFakeShutdownCalled = false;
|
||||
inline std::atomic<bool> g_liteFakeSyncBlock{false}; // when true, the "sync" command blocks
|
||||
|
||||
inline void resetLiteFakeCounters()
|
||||
{
|
||||
@@ -64,7 +68,12 @@ inline char* liteFakeExecute(const char* command, const char*)
|
||||
// tests/fixtures/lite/result_parsers.json), so the gateway/sync refresh path parses.
|
||||
if (command) {
|
||||
const char* c = command;
|
||||
if (std::strcmp(c, "sync") == 0) return liteFakeDup("{\"result\":\"success\"}");
|
||||
if (std::strcmp(c, "sync") == 0) {
|
||||
// Simulate the real backend's blocking full sync when requested, so tests can
|
||||
// verify shutdown doesn't hang on an in-flight sync.
|
||||
while (g_liteFakeSyncBlock.load()) std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
return liteFakeDup("{\"result\":\"success\"}");
|
||||
}
|
||||
if (std::strcmp(c, "syncstatus") == 0) // real backend shape: "syncing" is a string
|
||||
return liteFakeDup("{\"syncing\":\"true\",\"synced_blocks\":1000,\"total_blocks\":1000}");
|
||||
if (std::strcmp(c, "balance") == 0)
|
||||
|
||||
Reference in New Issue
Block a user