diff --git a/Session/Settings/DeveloperSettingsViewModel.swift b/Session/Settings/DeveloperSettingsViewModel.swift index 98081a4dc..e6802dc57 100644 --- a/Session/Settings/DeveloperSettingsViewModel.swift +++ b/Session/Settings/DeveloperSettingsViewModel.swift @@ -658,6 +658,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// Stop all pollers dependencies[singleton: .currentUserPoller].stop() dependencies.remove(cache: .groupPollers) + dependencies.remove(cache: .communityPollers) /// Reset the network dependencies.mutate(cache: .libSessionNetwork) { @@ -677,6 +678,9 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, /// Clear the snodeAPI caches dependencies.remove(cache: .snodeAPI) + /// Remove the libSession state + dependencies.remove(cache: .libSession) + /// Remove any network-specific data dependencies[singleton: .storage].write { [dependencies] db in let userSessionId: SessionId = dependencies[cache: .general].sessionId @@ -698,9 +702,6 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, _ = try ConfigDump.deleteAll(db) } - /// Remove the libSession state - dependencies.remove(cache: .libSession) - Log.info("[DevSettings] Reloading state for \(String(describing: updatedNetwork))") /// Update to the new `ServiceNetwork` diff --git a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift index fc0ad463b..560cadcbb 100644 --- a/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift +++ b/SessionMessagingKit/LibSession/Config Handling/LibSession+SharedGroup.swift @@ -175,9 +175,7 @@ internal extension LibSession { using dependencies: Dependencies ) { dependencies.mutate(cache: .libSession) { cache in - cache.setConfig(for: .groupKeys, sessionId: groupSessionId, to: nil) - cache.setConfig(for: .groupInfo, sessionId: groupSessionId, to: nil) - cache.setConfig(for: .groupMembers, sessionId: groupSessionId, to: nil) + cache.removeConfigs(for: groupSessionId) } _ = try? ConfigDump @@ -347,9 +345,7 @@ internal extension LibSessionCacheType { _ db: Database, groupSessionId: SessionId ) { - setConfig(for: .groupKeys, sessionId: groupSessionId, to: nil) - setConfig(for: .groupInfo, sessionId: groupSessionId, to: nil) - setConfig(for: .groupMembers, sessionId: groupSessionId, to: nil) + removeConfigs(for: groupSessionId) _ = try? ConfigDump .filter(ConfigDump.Columns.sessionId == groupSessionId.hexString) diff --git a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift index 72154b816..1c76de9fb 100644 --- a/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift +++ b/SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift @@ -93,6 +93,40 @@ private class ConfigStore { } deinit { + /// Group configs are a little complicated because they contain the info & members confs so we need some special handling to + /// properly free memory here, firstly we need to retrieve all groupKeys configs + let groupKeysConfigs: [(key: Key, value: LibSession.Config)] = store + .filter { _, config in + switch config { + case .groupKeys: return true + default: return false + } + } + + /// Now we remove all configss associated to the same sessionId from the store + groupKeysConfigs.forEach { key, _ in + ConfigDump.Variant.allCases.forEach { store.removeValue(forKey: Key(sessionId: key.sessionId, variant: $0)) } + } + + /// Then free the group configs + groupKeysConfigs.forEach { _, config in + switch config { + case .groupKeys(let keysConf, let infoConf, let membersConf): + groups_keys_free(keysConf) + config_free(infoConf) + config_free(membersConf) + + default: break + } + } + + /// Finally we free any remaining configs + store.forEach { _, config in + switch config { + case .invalid, .groupKeys: break // Shouldn't happen + case .object(let conf): config_free(conf) + } + } store.removeAll() } } @@ -334,10 +368,42 @@ public extension LibSession { return configStore[sessionId, variant] } - public func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: Config?) { + public func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: Config) { configStore[sessionId, variant] = config } + public func removeConfigs(for sessionId: SessionId) { + // First retrieve the configs stored for the sessionId + let configs: [LibSession.Config] = configStore[sessionId] + let keysConfig: LibSession.Config? = configs.first { config in + switch config { + case .groupKeys: return true + default: return false + } + } + + // Then remove them from the ConfigStore (can't have something else accessing them) + ConfigDump.Variant.allCases.forEach { configStore[sessionId, $0] = nil } + + // Finally we need to free them (if we got a `groupKeys` config then that includes + // the other confs for that sessionId so we can free them all at once, otherwise loop + // and freee everything + switch keysConfig { + case .groupKeys(let keysConf, let infoConf, let membersConf): + groups_keys_free(keysConf) + config_free(infoConf) + config_free(membersConf) + + default: + configs.forEach { config in + switch config { + case .invalid, .groupKeys: break // Should be handled above + case .object(let conf): config_free(conf) + } + } + } + } + public func createDump( config: Config?, for variant: ConfigDump.Variant, @@ -651,7 +717,8 @@ public protocol LibSessionCacheType: LibSessionImmutableCacheType, MutableCacheT userEd25519KeyPair: KeyPair ) func config(for variant: ConfigDump.Variant, sessionId: SessionId) -> LibSession.Config? - func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config?) + func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config) + func removeConfigs(for sessionId: SessionId) func createDump( config: LibSession.Config?, for variant: ConfigDump.Variant, @@ -717,7 +784,8 @@ private final class NoopLibSessionCache: LibSessionCacheType { userEd25519KeyPair: KeyPair ) {} func config(for variant: ConfigDump.Variant, sessionId: SessionId) -> LibSession.Config? { return nil } - func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config?) {} + func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config) {} + func removeConfigs(for sessionId: SessionId) {} func createDump( config: LibSession.Config?, for variant: ConfigDump.Variant, diff --git a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift index d5e5f8915..5b0c2917a 100644 --- a/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift +++ b/SessionMessagingKitTests/LibSession/LibSessionGroupInfoSpec.swift @@ -79,6 +79,7 @@ class LibSessionGroupInfoSpec: QuickSpec { _ = user_groups_init(&conf, &secretKey, nil, 0, nil) cache.when { $0.setConfig(for: .any, sessionId: .any, to: .any) }.thenReturn(()) + cache.when { $0.removeConfigs(for: .any) }.thenReturn(()) cache.when { $0.config(for: .userGroups, sessionId: .any) } .thenReturn(.object(conf)) cache.when { $0.config(for: .groupInfo, sessionId: .any) } diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift index 9a03d1210..01e14a22a 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageReceiverGroupsSpec.swift @@ -183,9 +183,8 @@ class MessageReceiverGroupsSpec: QuickSpec { initialSetup: { cache in let userSessionId: SessionId = SessionId(.standard, hex: TestConstants.publicKey) - cache - .when { $0.setConfig(for: .any, sessionId: .any, to: .any) } - .thenReturn(()) + cache.when { $0.setConfig(for: .any, sessionId: .any, to: .any) }.thenReturn(()) + cache.when { $0.removeConfigs(for: .any) }.thenReturn(()) cache .when { $0.config(for: .userGroups, sessionId: userSessionId) } .thenReturn(userGroupsConfig) @@ -2914,15 +2913,7 @@ class MessageReceiverGroupsSpec: QuickSpec { expect(mockLibSessionCache) .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupKeys, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupInfo, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupMembers, sessionId: groupId, to: nil) + $0.removeConfigs(for: groupId) }) } @@ -2939,15 +2930,7 @@ class MessageReceiverGroupsSpec: QuickSpec { expect(mockLibSessionCache) .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupKeys, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupInfo, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { - $0.setConfig(for: .groupMembers, sessionId: groupId, to: nil) + $0.removeConfigs(for: groupId) }) let dumps: [ConfigDump]? = mockStorage.read { db in diff --git a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift index b3cc4a3e7..c25f53f0a 100644 --- a/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift +++ b/SessionMessagingKitTests/Sending & Receiving/MessageSenderGroupsSpec.swift @@ -189,9 +189,8 @@ class MessageSenderGroupsSpec: QuickSpec { let userSessionId: SessionId = SessionId(.standard, hex: TestConstants.publicKey) cache.when { $0.isEmpty }.thenReturn(false) - cache - .when { $0.setConfig(for: .any, sessionId: .any, to: .any) } - .thenReturn(()) + cache.when { $0.setConfig(for: .any, sessionId: .any, to: .any) }.thenReturn(()) + cache.when { $0.removeConfigs(for: .any) }.thenReturn(()) cache .when { $0.config(for: .userGroups, sessionId: userSessionId) } .thenReturn(userGroupsConfig) @@ -548,15 +547,7 @@ class MessageSenderGroupsSpec: QuickSpec { expect(mockLibSessionCache) .to(call(.exactly(times: 1), matchingParameters: .all) { cache in - cache.setConfig(for: .groupInfo, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { cache in - cache.setConfig(for: .groupMembers, sessionId: groupId, to: nil) - }) - expect(mockLibSessionCache) - .to(call(.exactly(times: 1), matchingParameters: .all) { cache in - cache.setConfig(for: .groupKeys, sessionId: groupId, to: nil) + cache.removeConfigs(for: groupId) }) } diff --git a/SessionMessagingKitTests/_TestUtilities/MockLibSessionCache.swift b/SessionMessagingKitTests/_TestUtilities/MockLibSessionCache.swift index 65d7e3681..19b89cdf4 100644 --- a/SessionMessagingKitTests/_TestUtilities/MockLibSessionCache.swift +++ b/SessionMessagingKitTests/_TestUtilities/MockLibSessionCache.swift @@ -28,10 +28,14 @@ class MockLibSessionCache: Mock, LibSessionCacheType { return mock(args: [variant, sessionId]) } - func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config?) { + func setConfig(for variant: ConfigDump.Variant, sessionId: SessionId, to config: LibSession.Config) { mockNoReturn(args: [variant, sessionId, config]) } + func removeConfigs(for sessionId: SessionId) { + mockNoReturn(args: [sessionId]) + } + func createDump( config: LibSession.Config?, for variant: ConfigDump.Variant, diff --git a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift index 9172ef9d7..4bd5748bc 100644 --- a/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift +++ b/SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift @@ -325,7 +325,7 @@ class ThreadSettingsViewModelSpec: QuickSpec { ) )) expect(modal?.info.confirmTitle).to(equal("save".localized())) - expect(modal?.info.cancelTitle).to(equal("Reset")) + expect(modal?.info.cancelTitle).to(equal("remove".localized())) } // MARK: ---- does nothing if the name contains only white space diff --git a/SessionUtilitiesKit/Dependency Injection/Dependencies.swift b/SessionUtilitiesKit/Dependency Injection/Dependencies.swift index 38ac52b61..5d6644850 100644 --- a/SessionUtilitiesKit/Dependency Injection/Dependencies.swift +++ b/SessionUtilitiesKit/Dependency Injection/Dependencies.swift @@ -272,7 +272,7 @@ public enum DependenciesError: Error { private extension Dependencies { struct DependencyStorage { - var initializationTracker: [String: DispatchGroup] = [:] + var initializationLocks: [String: NSLock] = [:] var instances: [String: Value] = [:] enum Value { @@ -369,23 +369,23 @@ private extension Dependencies { storage.mutate { $0.instances.removeValue(forKey: key) } } - /// This function creates a `DispatchGroup` for the given identifier which allows us to block instance creation on a per-identifier basis + /// This function creates an `NSLock` for the given identifier which allows us to block instance creation on a per-identifier basis /// and avoid situations where multithreading could result in multiple instances of the same dependency being created concurrently /// - /// **Note:** This `DispatchGroup` is an additional mechanism on top of the `Atomic` because the interface is a little simpler + /// **Note:** This `NSLock` is an additional mechanism on top of the `Atomic` because the interface is a little simpler /// and we don't need to wrap every instance within `Atomic` this way @discardableResult private func threadSafeChange(for identifier: String, change: () -> T) -> T { - let group: DispatchGroup = storage.mutate { storage in - if let existing = storage.initializationTracker[identifier] { + let lock: NSLock = storage.mutate { storage in + if let existing = storage.initializationLocks[identifier] { return existing } - let group = DispatchGroup() - storage.initializationTracker[identifier] = group - return group + let lock: NSLock = NSLock() + storage.initializationLocks[identifier] = lock + return lock } - group.enter() - defer { group.leave() } + lock.lock() + defer { lock.unlock() } return change() }