fix(swift-sdk): stop SPV off the main thread - #4994
Draft
llbartekll wants to merge 1 commit into
Draft
llbartekll wants to merge 1 commit into
llbartekll wants to merge 1 commit into
Conversation
`PlatformWalletManager.stopSpv()` blocks its caller for as long as the native stop takes, and that stop waits for the SPV run loop to finish its current sync tick and drain its tasks: up to the 15 s stop budget plus a 2 s abort grace. Called from the main actor (as Dash Wallet iOS does during a network switch), it froze the UI for 10–16 s. A `sample` of a switch showed ~100% of main-thread samples inside `platform_wallet_manager_spv_stop`. Add an async `stopSpv()` overload (the same pattern as the async `createWallet` / `loadFromPersistor`). It runs the same native stop on a new per-manager serial queue instead of the calling thread: - admitted like the other async native entry points, so `shutdown()` waits for an in-flight stop before destroying the handle, and a stop requested once shutdown has begun throws `invalidHandle` before any native work; - `startSpv(config:)` throws `walletOperation` while an async stop is in flight, since the main actor is no longer blocked during the stop and a start issued meanwhile would race it; - not the process-wide `destroyQueue`, so a slow stop does not hold up other managers' creates, loads and teardowns. Existing sync callers keep the sync overload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
5 tasks
Collaborator
|
🕓 Review not started yet because this PR is a draft.
Commit 65209c1. Normal review starts when eligible; priority review starts as soon as a slot is available. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue being fixed or feature implemented
PlatformWalletManager.stopSpv()blocks its caller for as long as the native stop takes. That stop waits for the SPV run loop to finish its current sync tick and drain its tasks. In dash-spv,DashSpvClient::stop()waits for thesync_coordinatorlock, which therun()loop holds for a whole tick, and then up toTASK_JOIN_TIMEOUT(5 s) for tasks to exit. rs-platform-wallet bounds the whole stop at 15 s plus a 2 s abort grace.Dash Wallet iOS calls
stopSpv()from the main actor on every network switch, and the UI froze for 10–16 s. Asampleof the app during a switch had 2962 of 2967 main-thread samples insidePlatformWalletManager.stopSpv()→platform_wallet_manager_spv_stop→block_on. The dash-spv log for the same stop showsShutting down SyncCoordinator10 s after the stop request, thenShutdown timeout after 5s, 1 tasks may not have completed cleanly.What was done?
stopSpv()alongside the existing sync one. This follows the asynccreateWallet/loadFromPersistorpattern: in anasynccontext overload resolution picks it; sync contexts keep the sync variant.nativeTeardownCalls.spvStopseam) on a new per-manager serialspvStopQueue. That is a plain GCD thread, not the main thread and not the cooperative pool.destroyQueue: that queue is process-wide, and a 15 s stop there would hold up other managers' creates, loads and teardowns.admitNativeOp/finishNativeOp).shutdown()waits for an in-flight stop before destroying the handle, and a stop requested once shutdown has begun throwsinvalidHandlebefore any native work.startSpv(config:)throwswalletOperationwhile an async stop is in flight. The main actor is no longer blocked during the stop, so without this guard a start issued meanwhile would race it.Existing callers are unchanged. SwiftExampleApp and
IntegrationTestEnvcall the syncstopSpv()inside sync closures and keep the sync overload; the integration tests that alreadytry awaitit now get the async one.Not in scope: how long the dash-spv stop itself takes (the tick that holds the coordinator lock, the 5 s task join). That belongs in rust-dashcore.
How Has This Been Tested?
SwiftTests/SwiftDashSDKTests/PlatformWalletStopSpvTests.swift. It usesmakeForTestingwith a gated fakespvStop, the same approach asPlatformWalletCreateWalletTests:startSpvis refused while an async stop is in flight;shutdown()during an in-flight stop waits for it: every teardown step except the early shielded stop runs after the stop ends;stopSpv()aftershutdown()throwsinvalidHandlewithout the native call.xcodebuild test -scheme SwiftDashSDKon an iOS 26.5 simulator, together withPlatformWalletCreateWalletTestsandPlatformWalletLoadFromPersistorTests: 20/20 passed.startSpvguard and the admission removed, 3 of the 4 new tests fail.swift teston macOS. The disk could not fit a cold darwin build of the FFI, so CI will be the first macOS / Swift 6 run.sampleof the main thread and the app's main-thread stall monitor.Breaking Changes
None. The async overload is an addition, and the new
startSpvrefusal can only trigger while an async stop is running, which was impossible before.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code