Signal-iOS/SignalServiceKit/src/Storage/Database/SDSDatabaseQueue.swift

155 lines
4.6 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import GRDB
protocol SDSDatabaseQueue {
associatedtype ReadTransaction
associatedtype WriteTransaction
func read(block: @escaping (ReadTransaction) -> Void)
func write(block: @escaping (WriteTransaction) -> Void)
}
// MARK: -
// Serializes all transactions done using this queue.
@objc
public class GRDBDatabaseQueue: NSObject, SDSDatabaseQueue {
private let storageAdapter: GRDBDatabaseStorageAdapter
private let serialQueue = DispatchQueue(label: "org.signal.grdbDatabaseQueue")
init(storageAdapter: GRDBDatabaseStorageAdapter) {
self.storageAdapter = storageAdapter
}
@objc
public func read(block: @escaping (GRDBReadTransaction) -> Void) {
serialQueue.sync {
do {
try storageAdapter.read(block: block)
} catch {
owsFail("fatal error: \(error)")
}
}
}
@objc
public func write(block: @escaping (GRDBWriteTransaction) -> Void) {
serialQueue.sync {
do {
try storageAdapter.write(block: block)
} catch {
owsFail("fatal error: \(error)")
}
}
}
}
// MARK: -
class YAPDBDatabaseQueue: SDSDatabaseQueue {
private let databaseConnection: YapDatabaseConnection
public init(databaseConnection: YapDatabaseConnection) {
// We use DatabaseQueue's in places where we're especially concerned
// about data consistency. To help ensure that our instances aren't being
// mutated elsewhere we disable object caching on the connection.
databaseConnection.objectCacheEnabled = false
self.databaseConnection = databaseConnection
}
func read(block: @escaping (YapDatabaseReadTransaction) -> Void) {
databaseConnection.read(block)
}
func write(block: @escaping (YapDatabaseReadWriteTransaction) -> Void) {
databaseConnection.readWrite(block)
}
}
// MARK: -
@objc
public class SDSAnyDatabaseQueue: SDSTransactable, SDSDatabaseQueue {
// MARK: - Dependencies
private var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
// MARK: -
private let yapDatabaseQueue: YAPDBDatabaseQueue?
private let grdbDatabaseQueue: GRDBDatabaseQueue?
private let crossProcess: SDSCrossProcess
init(yapDatabaseQueue: YAPDBDatabaseQueue?,
grdbDatabaseQueue: GRDBDatabaseQueue?,
crossProcess: SDSCrossProcess) {
self.yapDatabaseQueue = yapDatabaseQueue
self.grdbDatabaseQueue = grdbDatabaseQueue
self.crossProcess = crossProcess
}
@objc
public override func read(block: @escaping (SDSAnyReadTransaction) -> Void) {
switch databaseStorage.dataStoreForReads {
case .grdb:
guard let grdbDatabaseQueue = grdbDatabaseQueue else {
owsFail("Missing grdbDatabaseQueue.")
}
grdbDatabaseQueue.read { block($0.asAnyRead) }
case .ydb:
guard let yapDatabaseQueue = yapDatabaseQueue else {
owsFail("Missing grdbDatabaseQueue.")
}
yapDatabaseQueue.read { block($0.asAnyRead) }
}
}
func write(block: @escaping (SDSAnyWriteTransaction) -> Void) {
write(file: #file,
function: #function,
line: #line,
block: block)
}
@objc
public override func write(file: String = #file,
function: String = #function,
line: Int = #line,
block: @escaping (SDSAnyWriteTransaction) -> Void) {
let benchTitle = "Slow Write Transaction \(SDSDatabaseStorage.owsFormatLogMessage(file: file, function: function, line: line))"
switch databaseStorage.dataStoreForWrites {
case .grdb:
guard let grdbDatabaseQueue = grdbDatabaseQueue else {
owsFail("Missing grdbDatabaseQueue.")
}
grdbDatabaseQueue.write { transaction in
Bench(title: benchTitle, logIfLongerThan: 0.1) {
block(transaction.asAnyWrite)
}
}
case .ydb:
guard let yapDatabaseQueue = yapDatabaseQueue else {
owsFail("Missing grdbDatabaseQueue.")
}
yapDatabaseQueue.write { transaction in
Bench(title: benchTitle, logIfLongerThan: 0.1) {
block(transaction.asAnyWrite)
}
}
}
crossProcess.notifyChangedAsync()
}
}