From adac932076e071e40124443ac419d3c7543a31b5 Mon Sep 17 00:00:00 2001 From: svojsu Date: Thu, 18 Sep 2025 21:15:01 +0300 Subject: [PATCH] add locks for tabs manager state changes --- .../DAppBrowserTabManager+Singleton.swift | 3 + .../DAppBrowserTabManager.swift | 97 ++++++++++++++++--- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager+Singleton.swift b/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager+Singleton.swift index 7cfbad9ed4..0299a00f3a 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager+Singleton.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager+Singleton.swift @@ -32,11 +32,14 @@ extension DAppBrowserTabManager { logger: logger ) + let workingQueue = DispatchQueue.global(qos: .userInitiated) + return DAppBrowserTabManager( fileRepository: renderFilesRepository, tabsSubscriptionFactory: tabsSubscriptionFactory, walletListLocalSubscriptionFactory: walletListLocalSubscriptionFactory, repository: AnyDataProviderRepository(coreDataRepository), + workingQueue: workingQueue, operationQueue: operationQueue, logger: logger ) diff --git a/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager.swift b/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager.swift index 4dc6363153..caf588770e 100644 --- a/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager.swift +++ b/novawallet/Modules/DApp/DAppBrowser/Tabs/DAppBrowserTabManager/DAppBrowserTabManager.swift @@ -10,16 +10,21 @@ final class DAppBrowserTabManager { private let fileRepository: WebViewRenderFilesOperationFactoryProtocol private let repository: AnyDataProviderRepository private let operationQueue: OperationQueue + private let workingQueue: DispatchQueue private let observerQueue: DispatchQueue private let logger: LoggerProtocol + private let transportStates: InMemoryCache = .init() + + private let callStore = CancellableCallStore() + private let mutex = NSLock() + private var metaAccount: MetaAccountModel? private var browserTabProvider: StreamableProvider? private var selectedWalletProvider: StreamableProvider? - private var transportStates: InMemoryCache = .init() private var observableTabs: DAppBrowserTabsObservable = .init(state: .init()) init( @@ -28,6 +33,7 @@ final class DAppBrowserTabManager { walletListLocalSubscriptionFactory: WalletListLocalSubscriptionFactoryProtocol, repository: AnyDataProviderRepository, observerQueue: DispatchQueue = .main, + workingQueue: DispatchQueue, operationQueue: OperationQueue, logger: LoggerProtocol ) { @@ -36,6 +42,7 @@ final class DAppBrowserTabManager { self.walletListLocalSubscriptionFactory = walletListLocalSubscriptionFactory self.fileRepository = fileRepository self.operationQueue = operationQueue + self.workingQueue = workingQueue self.observerQueue = observerQueue self.logger = logger @@ -51,7 +58,7 @@ private extension DAppBrowserTabManager { } func clearInMemory() { - transportStates = .init() + transportStates.removeAllValues() observableTabs.state.removeAllValues() } @@ -285,6 +292,38 @@ private extension DAppBrowserTabManager { return tab } + + func handleWalletChange(_ managedMetaAccount: ManagedMetaAccountModel?) { + let walletChangeInfo = evaluateWalletChange(managedMetaAccount) + + guard walletChangeInfo.didChange else { + return + } + + performWalletTransition(to: walletChangeInfo.newMetaAccount) + } + + func evaluateWalletChange(_ managedMetaAccount: ManagedMetaAccountModel?) -> WalletChangeInfo { + let newMetaId = managedMetaAccount?.info.metaId + let currentMetaId = metaAccount?.metaId + + guard currentMetaId != newMetaId else { + return WalletChangeInfo(didChange: false, newMetaAccount: nil) + } + + let newMetaAccount = managedMetaAccount?.info + metaAccount = newMetaAccount + + return WalletChangeInfo(didChange: true, newMetaAccount: newMetaAccount) + } + + func performWalletTransition(to newMetaAccount: MetaAccountModel?) { + observableTabs.state.removeAllValues() + + guard let newMetaAccount else { return } + + browserTabProvider = subscribeToBrowserTabs(newMetaAccount.metaId) + } } // MARK: DAppBrowserTabLocalSubscriber @@ -293,6 +332,9 @@ extension DAppBrowserTabManager: DAppBrowserTabLocalSubscriber, DAppBrowserTabLo func handleBrowserTabs( result: Result<[DataProviderChange], any Error> ) { + mutex.lock() + defer { mutex.unlock() } + switch result { case let .success(changes): apply(changes) @@ -308,18 +350,12 @@ extension DAppBrowserTabManager: WalletListLocalStorageSubscriber, WalletListLoc func handleSelectedWallet( result: Result ) { + mutex.lock() + defer { mutex.unlock() } + switch result { case let .success(managedMetaAccount): - guard metaAccount?.metaId != managedMetaAccount?.info.metaId else { - return - } - - observableTabs.state.removeAllValues() - metaAccount = managedMetaAccount?.info - - guard let metaAccount else { return } - - browserTabProvider = subscribeToBrowserTabs(metaAccount.metaId) + handleWalletChange(managedMetaAccount) case let .failure(error): logger.error("Failed on WalletList local subscription with error: \(error.localizedDescription)") } @@ -330,10 +366,16 @@ extension DAppBrowserTabManager: WalletListLocalStorageSubscriber, WalletListLoc extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { func retrieveTab(with id: UUID) -> CompoundOperationWrapper { - retrieveWrapper(for: id) + mutex.lock() + defer { mutex.unlock() } + + return retrieveWrapper(for: id) } func getAllTabs(for metaIds: Set?) -> CompoundOperationWrapper<[DAppBrowserTab]> { + mutex.lock() + defer { mutex.unlock() } + if let metaIds { return tabsFetchWrapper(for: metaIds) } else { @@ -359,7 +401,7 @@ extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { execute( wrapper: wrapper, inOperationQueue: operationQueue, - runningCallbackIn: .main, + runningCallbackIn: workingQueue, callbackClosure: { [weak self] _ in self?.transportStates.removeValue(for: id) } @@ -376,6 +418,9 @@ extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { with id: UUID, render: DAppBrowserTabRenderProtocol ) -> CompoundOperationWrapper { + mutex.lock() + defer { mutex.unlock() } + guard let tab = observableTabs.state.fetchValue(for: id) else { return .createWithResult(()) } @@ -411,16 +456,21 @@ extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { } func removeAll(for metaIds: Set?) { + mutex.lock() + defer { mutex.unlock() } + let wrapper = removeAllWrapper(metaIds) execute( wrapper: wrapper, inOperationQueue: operationQueue, - runningCallbackIn: .main + runningCallbackIn: workingQueue ) { [weak self] result in switch result { case .success: + self?.mutex.lock() self?.clearInMemory() + self?.mutex.unlock() case .failure: self?.logger.warning("\(String(describing: self)) Failed on tabs deletion operation") } @@ -428,13 +478,19 @@ extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { } func removeAllWrapper(for metaIds: Set?) -> CompoundOperationWrapper> { - removeAllWrapper(metaIds) + mutex.lock() + defer { mutex.unlock() } + + return removeAllWrapper(metaIds) } func addObserver( _ observer: DAppBrowserTabsObserver, sendOnSubscription: Bool ) { + mutex.lock() + defer { mutex.unlock() } + observableTabs.addObserver( with: observer, sendStateOnSubscription: sendOnSubscription, @@ -448,3 +504,12 @@ extension DAppBrowserTabManager: DAppBrowserTabManagerProtocol { } } } + +// MARK: - Private types + +private extension DAppBrowserTabManager { + struct WalletChangeInfo { + let didChange: Bool + let newMetaAccount: MetaAccountModel? + } +}