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:
@@ -85,10 +85,12 @@ public:
|
||||
LiteWalletLifecycleResult restoreWallet(LiteWalletRestoreRequest request);
|
||||
|
||||
bool syncStarted() const { return syncStarted_; }
|
||||
bool syncComplete() const { return syncDone_ && syncDone_->load(); }
|
||||
|
||||
// Begin background sync on the backend (idempotent enough to call once a wallet is ready;
|
||||
// also invoked automatically when a lifecycle op produces a ready wallet).
|
||||
LiteSyncStartResult startSync();
|
||||
// Launch the backend sync on a detached background thread (NON-blocking; the backend's
|
||||
// `sync` command runs a full, uninterruptible chain scan). Auto-invoked when a lifecycle
|
||||
// op produces a ready wallet; safe to call once.
|
||||
void startSync();
|
||||
|
||||
// Poll sync status + fetch balance/addresses/transactions, and apply the result into the
|
||||
// app's WalletState. Returns true if state was updated. Safe no-op when no wallet is open.
|
||||
@@ -110,7 +112,10 @@ private:
|
||||
void stopWorker();
|
||||
void workerLoop();
|
||||
|
||||
LiteClientBridge bridge_; // the single owned bridge; services below borrow &bridge_
|
||||
// The bridge is shared (not just owned) so the detached, uninterruptible sync thread can
|
||||
// safely outlive the controller: it holds a ref, so the underlying bridge is destroyed
|
||||
// (and litelib_shutdown called) only once BOTH the controller and a running sync release it.
|
||||
std::shared_ptr<LiteClientBridge> bridge_;
|
||||
LiteWalletLifecycleService lifecycle_;
|
||||
LiteWalletGateway gateway_;
|
||||
LiteSyncService sync_;
|
||||
@@ -119,14 +124,19 @@ private:
|
||||
std::atomic<bool> syncStarted_{false};
|
||||
WalletBackendStatus status_; // written only on the main thread (lifecycle ops)
|
||||
|
||||
// Background refresh worker.
|
||||
// Detached background sync (backend `sync` is a blocking, uninterruptible full scan).
|
||||
std::thread syncThread_;
|
||||
bool syncLaunched_ = false;
|
||||
std::shared_ptr<std::atomic<bool>> syncDone_ = std::make_shared<std::atomic<bool>>(false);
|
||||
|
||||
// Joinable background refresh worker (fast iterations: syncstatus, plus data once synced).
|
||||
std::thread worker_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::mutex wakeMutex_;
|
||||
std::condition_variable wakeCv_;
|
||||
std::mutex modelMutex_;
|
||||
std::optional<LiteWalletAppRefreshModel> pendingModel_; // guarded by modelMutex_
|
||||
static constexpr int kRefreshIntervalMs = 4000;
|
||||
static constexpr int kRefreshIntervalMs = 2000;
|
||||
};
|
||||
|
||||
} // namespace wallet
|
||||
|
||||
Reference in New Issue
Block a user