Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions Bitkit/Services/BackupService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class BackupService {
}

VssStoreIdProvider.shared.clearCache()
VssBackupClient.shared.reset()
await VssBackupClient.shared.reset()

Logger.debug("Full restore starting", context: "BackupService")

Expand Down Expand Up @@ -504,8 +504,6 @@ class BackupService {

func getLatestBackupTime() async -> UInt64? {
do {
try await vssBackupClient.setup()

let timestamps = await withTaskGroup(of: UInt64?.self) { group in
for category in BackupCategory.allCases where category != .lightningConnections {
group.addTask {
Expand Down
78 changes: 56 additions & 22 deletions Bitkit/Services/VssBackupClient.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,69 @@
import Foundation
import VssRustClientFfi

/// Actor to coordinate VSS client setup (ensures only one setup runs at a time)
private actor VssSetupCoordinator {
private enum SetupState {
case idle
case inProgress(Task<Void, Error>)
case completed
}

private var state: SetupState = .idle

func awaitSetup(setupAction: @escaping () async throws -> Void) async throws {
switch state {
case .completed:
Logger.debug("VssSetupCoordinator: already completed, returning", context: "VssBackupClient")
return

case let .inProgress(existingTask):
Logger.debug("VssSetupCoordinator: setup in progress, waiting for existing task", context: "VssBackupClient")
try await existingTask.value
Logger.debug("VssSetupCoordinator: existing task completed", context: "VssBackupClient")
return

case .idle:
Logger.debug("VssSetupCoordinator: idle, starting new setup", context: "VssBackupClient")
let task = Task {
try await setupAction()
}
state = .inProgress(task)

do {
try await task.value
state = .completed
Logger.debug("VssSetupCoordinator: setup completed successfully", context: "VssBackupClient")
} catch {
// Reset on any error to allow retry attempts
state = .idle
Logger.debug("VssSetupCoordinator: setup failed, resetting to idle", context: "VssBackupClient")
throw error
}
}
}

func reset() {
Logger.debug("VssSetupCoordinator: reset called", context: "VssBackupClient")
if case let .inProgress(task) = state {
task.cancel()
}
state = .idle
}
}

class VssBackupClient {
static let shared = VssBackupClient()

private var isSetup: Task<Void, Error>?
private let setupCoordinator = VssSetupCoordinator()

private init() {}

func reset() {
isSetup = nil
func reset() async {
await setupCoordinator.reset()
}

func setup(walletIndex: Int = 0) async throws {
private func setup(walletIndex: Int = 0) async throws {
do {
try await withTimeout(seconds: 30) {
Logger.debug("VSS client setting up…", context: "VssBackupClient")
Expand Down Expand Up @@ -87,26 +138,9 @@ class VssBackupClient {
}

private func awaitSetup() async throws {
if let existingSetup = isSetup {
do {
try await existingSetup.value
} catch let error as CancellationError {
isSetup = nil
throw error
}
}

let setupTask = Task {
try await setupCoordinator.awaitSetup { [self] in
try await setup()
}
isSetup = setupTask

do {
try await setupTask.value
} catch let error as CancellationError {
isSetup = nil
throw error
}
}

private func withTimeout<T>(seconds: TimeInterval, operation: @escaping () async throws -> T) async throws -> T {
Expand Down
2 changes: 1 addition & 1 deletion Bitkit/Utilities/AppReset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ enum AppReset {

// Stop backup observers and reset VSS client
await BackupService.shared.stopObservingBackups()
VssBackupClient.shared.reset()
await VssBackupClient.shared.reset()

// Stop node and wipe LDK persistence via the wallet API.
try await wallet.wipe()
Expand Down
Loading