mirror of
git://jb55.com/damus
synced 2024-09-20 20:16:58 +00:00
4e447ddbed
Many different parts of the codebase could be opening transactions when somewhere higher in the heirarchy on the main thread might already have an active transaction. This can lead to failed transaction opening which is bad. Instead of relying on passing down the transaction to subviews, lets keep track of the active transactions in a thread-local dictionary. That way whenever we create a new transaction we can inherit the one that is already active in the current thread. Inherited transactions don't end the query when they are garbage collected, we still expect the first-opened query to do this.
116 lines
3.5 KiB
Swift
116 lines
3.5 KiB
Swift
//
|
|
// NdbTx.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-08-30.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
#if TXNDEBUG
|
|
fileprivate var txn_count: Int = 0
|
|
#endif
|
|
|
|
// Would use struct and ~Copyable but generics aren't supported well
|
|
class NdbTxn<T> {
|
|
var txn: ndb_txn
|
|
private var val: T!
|
|
var moved: Bool
|
|
var inherited: Bool
|
|
|
|
init(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }) {
|
|
#if TXNDEBUG
|
|
txn_count += 1
|
|
print("opening transaction \(txn_count)")
|
|
#endif
|
|
if let active_txn = Thread.current.threadDictionary["ndb_txn"] as? ndb_txn {
|
|
// some parent thread is active, use that instead
|
|
self.txn = active_txn
|
|
self.inherited = true
|
|
} else {
|
|
self.txn = ndb_txn()
|
|
let _ = ndb_begin_query(ndb.ndb.ndb, &self.txn)
|
|
Thread.current.threadDictionary["ndb_txn"] = self.txn
|
|
self.inherited = false
|
|
}
|
|
self.moved = false
|
|
self.val = with(self)
|
|
}
|
|
|
|
private init(txn: ndb_txn, val: T) {
|
|
self.txn = txn
|
|
self.val = val
|
|
self.moved = false
|
|
self.inherited = false
|
|
}
|
|
|
|
/// Only access temporarily! Do not store database references for longterm use. If it's a primitive type you
|
|
/// can retrieve this value with `.value`
|
|
var unsafeUnownedValue: T {
|
|
precondition(!moved)
|
|
return val
|
|
}
|
|
|
|
deinit {
|
|
if moved || inherited {
|
|
return
|
|
}
|
|
|
|
#if TXNDEBUG
|
|
txn_count -= 1;
|
|
print("closing transaction \(txn_count)")
|
|
#endif
|
|
ndb_end_query(&self.txn)
|
|
Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
|
|
}
|
|
|
|
// functor
|
|
func map<Y>(_ transform: (T) -> Y) -> NdbTxn<Y> {
|
|
self.moved = true
|
|
return .init(txn: self.txn, val: transform(val))
|
|
}
|
|
|
|
// comonad!?
|
|
// useful for moving ownership of a transaction to another value
|
|
func extend<Y>(_ with: (NdbTxn<T>) -> Y) -> NdbTxn<Y> {
|
|
self.moved = true
|
|
return .init(txn: self.txn, val: with(self))
|
|
}
|
|
}
|
|
|
|
protocol OptionalType {
|
|
associatedtype Wrapped
|
|
var optional: Wrapped? { get }
|
|
}
|
|
|
|
extension Optional: OptionalType {
|
|
typealias Wrapped = Wrapped
|
|
|
|
var optional: Wrapped? {
|
|
return self
|
|
}
|
|
}
|
|
|
|
extension NdbTxn where T: OptionalType {
|
|
func collect() -> NdbTxn<T.Wrapped>? {
|
|
guard let unwrappedVal: T.Wrapped = val.optional else {
|
|
return nil
|
|
}
|
|
self.moved = true
|
|
return NdbTxn<T.Wrapped>(txn: self.txn, val: unwrappedVal)
|
|
}
|
|
}
|
|
|
|
extension NdbTxn where T == Bool { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == Bool? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == Int { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == Int? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == Double { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == Double? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == UInt64 { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == UInt64? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == String { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == String? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == NoteId? { var value: T { return self.unsafeUnownedValue } }
|
|
extension NdbTxn where T == NoteId { var value: T { return self.unsafeUnownedValue } }
|