From 2c15613d690067c97d6a6c734dbf090575eec18b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 1 Jun 2020 10:55:56 +1000 Subject: [PATCH 1/3] Cache swarms --- .../src/Loki/API/LokiAPI+SwarmAPI.swift | 30 +++++++++++++++-- .../src/Loki/API/LokiAPITarget.swift | 2 ++ .../Database/OWSPrimaryStorage+Loki.swift | 33 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift index 4bbb2749c..1d8d56fa8 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+SwarmAPI.swift @@ -14,13 +14,20 @@ public extension LokiAPI { internal static let snodeFailureThreshold = 2 // MARK: Caching - internal static var swarmCache: [String:[LokiAPITarget]] = [:] + internal static var swarmCache: [String:[LokiAPITarget]] = [:] // TODO: Make this set based? internal static func dropSnodeFromSwarmIfNeeded(_ target: LokiAPITarget, hexEncodedPublicKey: String) { let swarm = LokiAPI.swarmCache[hexEncodedPublicKey] if var swarm = swarm, let index = swarm.firstIndex(of: target) { swarm.remove(at: index) LokiAPI.swarmCache[hexEncodedPublicKey] = swarm + // Dispatch async on the main queue to avoid nested write transactions + DispatchQueue.main.async { + let storage = OWSPrimaryStorage.shared() + storage.dbReadWriteConnection.readWrite { transaction in + storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction) + } + } } } @@ -96,11 +103,30 @@ public extension LokiAPI { } internal static func getSwarm(for hexEncodedPublicKey: String) -> Promise<[LokiAPITarget]> { + if swarmCache[hexEncodedPublicKey] == nil { + storage.dbReadConnection.read { transaction in + swarmCache[hexEncodedPublicKey] = storage.getSwarm(for: hexEncodedPublicKey, in: transaction) + } + } if let cachedSwarm = swarmCache[hexEncodedPublicKey], cachedSwarm.count >= minimumSwarmSnodeCount { return Promise<[LokiAPITarget]> { $0.fulfill(cachedSwarm) } } else { + print("[Loki] Getting swarm for: \(hexEncodedPublicKey).") let parameters: [String:Any] = [ "pubKey" : hexEncodedPublicKey ] - return getRandomSnode().then(on: workQueue) { invoke(.getSwarm, on: $0, associatedWith: hexEncodedPublicKey, parameters: parameters) }.map { parseTargets(from: $0) }.get { swarmCache[hexEncodedPublicKey] = $0 } + return getRandomSnode().then(on: workQueue) { + invoke(.getSwarm, on: $0, associatedWith: hexEncodedPublicKey, parameters: parameters) + }.map { + parseTargets(from: $0) + }.get { swarm in + swarmCache[hexEncodedPublicKey] = swarm + // Dispatch async on the main queue to avoid nested write transactions + DispatchQueue.main.async { + let storage = OWSPrimaryStorage.shared() + storage.dbReadWriteConnection.readWrite { transaction in + storage.setSwarm(swarm, for: hexEncodedPublicKey, in: transaction) + } + } + } } } diff --git a/SignalServiceKit/src/Loki/API/LokiAPITarget.swift b/SignalServiceKit/src/Loki/API/LokiAPITarget.swift index 4b64ef47d..ed6c86845 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPITarget.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPITarget.swift @@ -1,4 +1,6 @@ +public typealias Snode = LokiAPITarget + /// Either a service node or another client if P2P is enabled. public final class LokiAPITarget : NSObject, NSCoding { internal let address: String diff --git a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift index d67e802a6..d519e8904 100644 --- a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift +++ b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift @@ -1,4 +1,6 @@ +// TODO: Make this strongly typed like LKUserDefaults + public extension OWSPrimaryStorage { // MARK: - Snode Pool @@ -30,6 +32,37 @@ public extension OWSPrimaryStorage { + // MARK: - Swarm + private func getSwarmCollection(for publicKey: String) -> String { + return "LokiSwarmCollection-\(publicKey)" + } + + public func setSwarm(_ swarm: [Snode], for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { + print("[Loki] Caching swarm for: \(publicKey).") + clearSwarm(for: publicKey, in: transaction) + let collection = getSwarmCollection(for: publicKey) + swarm.forEach { snode in + transaction.setObject(snode, forKey: snode.description, inCollection: collection) + } + } + + public func clearSwarm(for publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { + let collection = getSwarmCollection(for: publicKey) + transaction.removeAllObjects(inCollection: collection) + } + + public func getSwarm(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> [Snode] { + var result: [Snode] = [] + let collection = getSwarmCollection(for: publicKey) + transaction.enumerateKeysAndObjects(inCollection: collection) { _, object, _ in + guard let snode = object as? Snode else { return } + result.append(snode) + } + return result + } + + + // MARK: - Onion Request Path private static let onionRequestPathCollection = "LokiOnionRequestPathCollection" From 2a99498dd887e5182e6346dfb62f59f1c6d96b3f Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 1 Jun 2020 11:06:39 +1000 Subject: [PATCH 2/3] Use a high priority queue for message sending --- SignalServiceKit/src/Network/MessageSenderJobQueue.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SignalServiceKit/src/Network/MessageSenderJobQueue.swift b/SignalServiceKit/src/Network/MessageSenderJobQueue.swift index a27f3ba19..33ebfd6bf 100644 --- a/SignalServiceKit/src/Network/MessageSenderJobQueue.swift +++ b/SignalServiceKit/src/Network/MessageSenderJobQueue.swift @@ -131,6 +131,7 @@ public class MessageSenderJobQueue: NSObject, JobQueue { let operationQueue = OperationQueue() operationQueue.name = "DefaultSendingQueue" operationQueue.maxConcurrentOperationCount = 1 + operationQueue.qualityOfService = .userInitiated return operationQueue }() From e11864c3b3dba5b475694f1cb64e6bad3cbf102b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 1 Jun 2020 11:06:50 +1000 Subject: [PATCH 3/3] Use a high priority queue for PoW calculation --- SignalServiceKit/src/Loki/API/LokiMessage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Loki/API/LokiMessage.swift b/SignalServiceKit/src/Loki/API/LokiMessage.swift index 2e72ba112..7479bca2e 100644 --- a/SignalServiceKit/src/Loki/API/LokiMessage.swift +++ b/SignalServiceKit/src/Loki/API/LokiMessage.swift @@ -49,7 +49,7 @@ public struct LokiMessage { /// - Returns: The promise of a new message with its `timestamp` and `nonce` set. public func calculatePoW() -> Promise { return Promise { seal in - DispatchQueue.global().async { + DispatchQueue.global(qos: .userInitiated).async { let now = NSDate.ows_millisecondTimeStamp() let dataAsString = self.data as! String // Safe because of how from(signalMessage:with:) is implemented if let nonce = ProofOfWork.calculate(data: dataAsString, pubKey: self.destination, timestamp: now, ttl: self.ttl) {