Cache device links in memory rather than on disk

pull/195/head
nielsandriesse 5 years ago
parent fcdc44d3fa
commit d6c24cebb8

@ -24,8 +24,8 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
/// Gets the device links associated with the given hex encoded public key from the /// Gets the device links associated with the given hex encoded public key from the
/// server and stores and returns the valid ones. /// server and stores and returns the valid ones.
public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String, usingCache: Bool = false) -> Promise<Set<DeviceLink>> { public static func getDeviceLinks(associatedWith hexEncodedPublicKey: String) -> Promise<Set<DeviceLink>> {
return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ], usingCache: usingCache) return getDeviceLinks(associatedWith: [ hexEncodedPublicKey ])
} }
@objc(getDeviceLinksAssociatedWithHexEncodedPublicKeys:) @objc(getDeviceLinksAssociatedWithHexEncodedPublicKeys:)
@ -35,7 +35,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
/// Gets the device links associated with the given hex encoded public keys from the /// Gets the device links associated with the given hex encoded public keys from the
/// server and stores and returns the valid ones. /// server and stores and returns the valid ones.
public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set<String>, usingCache: Bool = false) -> Promise<Set<DeviceLink>> { public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set<String>) -> Promise<Set<DeviceLink>> {
let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]" let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]"
print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).") print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).")
return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise<Set<DeviceLink>> in return getAuthToken(for: server).then(on: DispatchQueue.global()) { token -> Promise<Set<DeviceLink>> in
@ -84,23 +84,17 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
return deviceLink return deviceLink
} }
}) })
}.then(on: DispatchQueue.global()) { deviceLinks -> Promise<Set<DeviceLink>> in }.map(on: DispatchQueue.global()) { deviceLinks in
let (promise, seal) = Promise<Set<DeviceLink>>.pending() storage.cacheDeviceLinks(deviceLinks)
if usingCache { /*
storage.setDeviceLinksInCache(deviceLinks) // Dispatch async on the main queue to avoid nested write transactions
seal.fulfill(deviceLinks) DispatchQueue.main.async {
} else { storage.dbReadWriteConnection.readWrite { transaction in
// Dispatch async on the main queue to avoid nested write transactions storage.setDeviceLinks(deviceLinks, in: transaction)
DispatchQueue.main.async {
storage.dbReadWriteConnection.readWrite{ transaction in
storage.setDeviceLinks(deviceLinks, in: transaction)
}
// We have to wait for the device links to be stored because a lot of our logic relies
// on them being in the database
seal.fulfill(deviceLinks)
} }
} }
return promise */
return deviceLinks
} }
} }
} }

@ -1,19 +1,14 @@
public extension OWSPrimaryStorage { public extension OWSPrimaryStorage {
private static var deviceLinkCache: Set<DeviceLink> = []
private func getDeviceLinkCollection(for masterHexEncodedPublicKey: String) -> String { private func getDeviceLinkCollection(for masterHexEncodedPublicKey: String) -> String {
return "LokiDeviceLinkCollection-\(masterHexEncodedPublicKey)" return "LokiDeviceLinkCollection-\(masterHexEncodedPublicKey)"
} }
public func setDeviceLinksInCache(_ deviceLinks: Set<DeviceLink>) { public func cacheDeviceLinks(_ deviceLinks: Set<DeviceLink>) {
self.deviceLinkCache = deviceLinks OWSPrimaryStorage.deviceLinkCache.formUnion(deviceLinks)
}
public func syncDeviceLinkCacheToDatabase(in transaction: YapDatabaseReadWriteTransaction) {
if !self.deviceLinkCache.isEmpty {
self.setDeviceLinks(self.deviceLinkCache, in: transaction)
self.deviceLinkCache.removeAll()
}
} }
public func setDeviceLinks(_ deviceLinks: Set<DeviceLink>, in transaction: YapDatabaseReadWriteTransaction) { public func setDeviceLinks(_ deviceLinks: Set<DeviceLink>, in transaction: YapDatabaseReadWriteTransaction) {
@ -22,19 +17,24 @@ public extension OWSPrimaryStorage {
} }
public func addDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) { public func addDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) {
OWSPrimaryStorage.deviceLinkCache.insert(deviceLink)
/*
let collection = getDeviceLinkCollection(for: deviceLink.master.hexEncodedPublicKey) let collection = getDeviceLinkCollection(for: deviceLink.master.hexEncodedPublicKey)
transaction.setObject(deviceLink, forKey: deviceLink.slave.hexEncodedPublicKey, inCollection: collection) transaction.setObject(deviceLink, forKey: deviceLink.slave.hexEncodedPublicKey, inCollection: collection)
*/
} }
public func removeDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) { public func removeDeviceLink(_ deviceLink: DeviceLink, in transaction: YapDatabaseReadWriteTransaction) {
OWSPrimaryStorage.deviceLinkCache.remove(deviceLink)
/*
let collection = getDeviceLinkCollection(for: deviceLink.master.hexEncodedPublicKey) let collection = getDeviceLinkCollection(for: deviceLink.master.hexEncodedPublicKey)
transaction.removeObject(forKey: deviceLink.slave.hexEncodedPublicKey, inCollection: collection) transaction.removeObject(forKey: deviceLink.slave.hexEncodedPublicKey, inCollection: collection)
*/
} }
public func getDeviceLinks(for masterHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set<DeviceLink> { public func getDeviceLinks(for masterHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Set<DeviceLink> {
if !self.deviceLinkCache.isEmpty { return OWSPrimaryStorage.deviceLinkCache.filter { $0.master.hexEncodedPublicKey == masterHexEncodedPublicKey }
return self.deviceLinkCache /*
}
let collection = getDeviceLinkCollection(for: masterHexEncodedPublicKey) let collection = getDeviceLinkCollection(for: masterHexEncodedPublicKey)
guard !transaction.allKeys(inCollection: collection).isEmpty else { return [] } // Fixes a crash that used to occur on Josh's device guard !transaction.allKeys(inCollection: collection).isEmpty else { return [] } // Fixes a crash that used to occur on Josh's device
var result: Set<DeviceLink> = [] var result: Set<DeviceLink> = []
@ -43,9 +43,12 @@ public extension OWSPrimaryStorage {
result.insert(deviceLink) result.insert(deviceLink)
} }
return result return result
*/
} }
public func getDeviceLink(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> DeviceLink? { public func getDeviceLink(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> DeviceLink? {
return OWSPrimaryStorage.deviceLinkCache.filter { $0.slave.hexEncodedPublicKey == slaveHexEncodedPublicKey }.first
/*
let query = YapDatabaseQuery(string: "WHERE \(DeviceLinkIndex.slaveHexEncodedPublicKey) = ?", parameters: [ slaveHexEncodedPublicKey ]) let query = YapDatabaseQuery(string: "WHERE \(DeviceLinkIndex.slaveHexEncodedPublicKey) = ?", parameters: [ slaveHexEncodedPublicKey ])
let deviceLinks = DeviceLinkIndex.getDeviceLinks(for: query, in: transaction) let deviceLinks = DeviceLinkIndex.getDeviceLinks(for: query, in: transaction)
guard deviceLinks.count <= 1 else { guard deviceLinks.count <= 1 else {
@ -53,6 +56,7 @@ public extension OWSPrimaryStorage {
return nil return nil
} }
return deviceLinks.first return deviceLinks.first
*/
} }
public func getMasterHexEncodedPublicKey(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> String? { public func getMasterHexEncodedPublicKey(for slaveHexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> String? {

@ -24,7 +24,7 @@ public final class MultiDeviceProtocol : NSObject {
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
// MARK: - Settings // MARK: - Settings
public static let deviceLinkUpdateInterval: TimeInterval = 20 public static let deviceLinkUpdateInterval: TimeInterval = 60
// MARK: - Multi Device Destination // MARK: - Multi Device Destination
public struct MultiDeviceDestination : Hashable { public struct MultiDeviceDestination : Hashable {
@ -144,16 +144,11 @@ public final class MultiDeviceProtocol : NSObject {
} }
} }
@objc(updateDeviceLinksIfNeededForHexEncodedPublicKey:in:usingCache:) @objc(updateDeviceLinksIfNeededForHexEncodedPublicKey:in:)
public static func updateDeviceLinksIfNeeded(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction, usingCache: Bool = false) -> AnyPromise { public static func updateDeviceLinksIfNeeded(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise {
let promise = getMultiDeviceDestinations(for: hexEncodedPublicKey, in: transaction, usingCache: usingCache) let promise = getMultiDeviceDestinations(for: hexEncodedPublicKey, in: transaction)
return AnyPromise.from(promise) return AnyPromise.from(promise)
} }
@objc(syncDeviceLinkCacheToDatabaseInTransaction:)
public static func syncDeviceLinkCacheToDatabase(in transaction: YapDatabaseReadWriteTransaction) {
storage.syncDeviceLinkCacheToDatabase(in: transaction)
}
/// See [Auto-Generated Friend Requests](https://github.com/loki-project/session-protocol-docs/wiki/Auto-Generated-Friend-Requests) for more information. /// See [Auto-Generated Friend Requests](https://github.com/loki-project/session-protocol-docs/wiki/Auto-Generated-Friend-Requests) for more information.
@objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:) @objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:)
@ -289,7 +284,7 @@ public final class MultiDeviceProtocol : NSObject {
// Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C // Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C
public extension MultiDeviceProtocol { public extension MultiDeviceProtocol {
fileprivate static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction, usingCache: Bool = false) -> Promise<Set<MultiDeviceDestination>> { fileprivate static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> Promise<Set<MultiDeviceDestination>> {
let (promise, seal) = Promise<Set<MultiDeviceDestination>>.pending() let (promise, seal) = Promise<Set<MultiDeviceDestination>>.pending()
func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) { func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) {
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
@ -311,7 +306,7 @@ public extension MultiDeviceProtocol {
} }
if timeSinceLastUpdate > deviceLinkUpdateInterval { if timeSinceLastUpdate > deviceLinkUpdateInterval {
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, usingCache: usingCache).done(on: DispatchQueue.global()) { _ in LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey).done(on: DispatchQueue.global()) { _ in
getDestinations() getDestinations()
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date() lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
}.catch(on: DispatchQueue.global()) { error in }.catch(on: DispatchQueue.global()) { error in

@ -1266,18 +1266,16 @@ NS_ASSUME_NONNULL_BEGIN
// Loki: Update device links in a blocking way // Loki: Update device links in a blocking way
// FIXME: This is horrible for performance // FIXME: This is horrible for performance
// FIXME: ======== // FIXME: ========
// FIX: Using the cache for write to avoid deadlock
// The envelope source is set during UD decryption // The envelope source is set during UD decryption
if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil) { // Handled in LokiPublicChatPoller for open group messages if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil) { // Handled in LokiPublicChatPoller for open group messages
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[[LKMultiDeviceProtocol updateDeviceLinksIfNeededForHexEncodedPublicKey:envelope.source in:transaction usingCache: true].ensureOn(queue, ^() { [[LKMultiDeviceProtocol updateDeviceLinksIfNeededForHexEncodedPublicKey:envelope.source in:transaction].ensureOn(queue, ^() {
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}).catchOn(queue, ^(NSError *error) { }).catchOn(queue, ^(NSError *error) {
dispatch_semaphore_signal(semaphore); dispatch_semaphore_signal(semaphore);
}) retainUntilComplete]; }) retainUntilComplete];
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)); dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
[LKMultiDeviceProtocol syncDeviceLinkCacheToDatabaseInTransaction:transaction];
} }
// FIXME: ======== // FIXME: ========

@ -25,7 +25,6 @@ extern NSString *const OWSUIDatabaseConnectionNotificationsKey;
@property (nonatomic, readonly) YapDatabaseConnection *uiDatabaseConnection; @property (nonatomic, readonly) YapDatabaseConnection *uiDatabaseConnection;
@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection; @property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection; @property (nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection;
@property (nonatomic) NSSet<LKDeviceLink*> *deviceLinkCache;
- (void)updateUIDatabaseConnectionToLatest; - (void)updateUIDatabaseConnectionToLatest;

@ -76,8 +76,6 @@ void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage)
if (self) { if (self) {
[self loadDatabase]; [self loadDatabase];
_deviceLinkCache = [NSSet set];
_dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database]; _dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database];
_dbReadWriteConnection = [self newDatabaseConnection]; _dbReadWriteConnection = [self newDatabaseConnection];
_uiDatabaseConnection = [self newDatabaseConnection]; _uiDatabaseConnection = [self newDatabaseConnection];

Loading…
Cancel
Save