damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

NdbTxn.swift (9937B)


      1 //
      2 //  NdbTx.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-08-30.
      6 //
      7 
      8 import Foundation
      9 
     10 #if TXNDEBUG
     11 fileprivate var txn_count: Int = 0
     12 #endif
     13 
     14 // Would use struct and ~Copyable but generics aren't supported well
     15 class NdbTxn<T>: RawNdbTxnAccessible {
     16     var txn: ndb_txn
     17     private var val: T!
     18     var moved: Bool
     19     var inherited: Bool
     20     var ndb: Ndb
     21     var generation: Int
     22     var name: String
     23 
     24     static func pure(ndb: Ndb, val: T) -> NdbTxn<T> {
     25         .init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
     26     }
     27 
     28     init?(ndb: Ndb, with: (NdbTxn<T>) -> T = { _ in () }, name: String? = nil) {
     29         guard !ndb.is_closed else { return nil }
     30         self.name = name ?? "txn"
     31         self.ndb = ndb
     32         self.generation = ndb.generation
     33         if let active_txn = Thread.current.threadDictionary["ndb_txn"] as? ndb_txn,
     34            let txn_generation = Thread.current.threadDictionary["txn_generation"] as? Int,
     35            txn_generation == ndb.generation
     36         {
     37             // some parent thread is active, use that instead
     38             print("txn: inherited txn")
     39             self.txn = active_txn
     40             self.inherited = true
     41             self.generation = Thread.current.threadDictionary["txn_generation"] as! Int
     42         } else {
     43             self.txn = ndb_txn()
     44             guard !ndb.is_closed else { return nil }
     45             self.generation = ndb.generation
     46             #if TXNDEBUG
     47             txn_count += 1
     48             #endif
     49             let ok = ndb_begin_query(ndb.ndb.ndb, &self.txn) != 0
     50             if !ok {
     51                 return nil
     52             }
     53             self.generation = ndb.generation
     54             Thread.current.threadDictionary["ndb_txn"] = self.txn
     55             Thread.current.threadDictionary["txn_generation"] = ndb.generation
     56             self.inherited = false
     57         }
     58         #if TXNDEBUG
     59         print("txn: open  gen\(self.generation) '\(self.name)' \(txn_count)")
     60         #endif
     61         self.moved = false
     62         self.val = with(self)
     63     }
     64 
     65     private init(ndb: Ndb, txn: ndb_txn, val: T, generation: Int, inherited: Bool, name: String) {
     66         self.txn = txn
     67         self.val = val
     68         self.moved = false
     69         self.inherited = inherited
     70         self.ndb = ndb
     71         self.generation = generation
     72         self.name = name
     73     }
     74 
     75     /// Only access temporarily! Do not store database references for longterm use. If it's a primitive type you
     76     /// can retrieve this value with `.value`
     77     var unsafeUnownedValue: T {
     78         precondition(!moved)
     79         return val
     80     }
     81 
     82     deinit {
     83         if self.generation != ndb.generation {
     84             print("txn: OLD GENERATION (\(self.generation) != \(ndb.generation)), IGNORING")
     85             return
     86         }
     87         if inherited {
     88             print("txn: not closing. inherited ")
     89             return
     90         }
     91         if moved {
     92             //print("txn: not closing. moved")
     93             return
     94         }
     95         if ndb.is_closed {
     96             print("txn: not closing. db closed")
     97             return
     98         }
     99 
    100         #if TXNDEBUG
    101         txn_count -= 1;
    102         print("txn: close gen\(generation) '\(name)' \(txn_count)")
    103         #endif
    104         ndb_end_query(&self.txn)
    105         //self.skip_close = true
    106         Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
    107     }
    108 
    109     // functor
    110     func map<Y>(_ transform: (T) -> Y) -> NdbTxn<Y> {
    111         self.moved = true
    112         return .init(ndb: self.ndb, txn: self.txn, val: transform(val), generation: generation, inherited: inherited, name: self.name)
    113     }
    114 
    115     // comonad!?
    116     // useful for moving ownership of a transaction to another value
    117     func extend<Y>(_ with: (NdbTxn<T>) -> Y) -> NdbTxn<Y> {
    118         self.moved = true
    119         return .init(ndb: self.ndb, txn: self.txn, val: with(self), generation: generation, inherited: inherited, name: self.name)
    120     }
    121 }
    122 
    123 protocol RawNdbTxnAccessible: AnyObject {
    124     var txn: ndb_txn { get set }
    125 }
    126 
    127 class PlaceholderNdbTxn: RawNdbTxnAccessible {
    128     var txn: ndb_txn
    129     
    130     init(txn: ndb_txn) {
    131         self.txn = txn
    132     }
    133 }
    134 
    135 class SafeNdbTxn<T: ~Copyable> {
    136     var txn: ndb_txn
    137     var val: T!
    138     var moved: Bool
    139     var inherited: Bool
    140     var ndb: Ndb
    141     var generation: Int
    142     var name: String
    143 
    144     static func pure(ndb: Ndb, val: consuming T) -> SafeNdbTxn<T> {
    145         .init(ndb: ndb, txn: ndb_txn(), val: val, generation: ndb.generation, inherited: true, name: "pure_txn")
    146     }
    147     
    148     static func new(on ndb: Ndb, with valueGetter: (PlaceholderNdbTxn) -> T? = { _ in () }, name: String = "txn") -> SafeNdbTxn<T>? {
    149         guard !ndb.is_closed else { return nil }
    150         var generation = ndb.generation
    151         var txn: ndb_txn
    152         let inherited: Bool
    153         if let active_txn = Thread.current.threadDictionary["ndb_txn"] as? ndb_txn,
    154            let txn_generation = Thread.current.threadDictionary["txn_generation"] as? Int,
    155            txn_generation == ndb.generation
    156         {
    157             // some parent thread is active, use that instead
    158             print("txn: inherited txn")
    159             txn = active_txn
    160             inherited = true
    161             generation = Thread.current.threadDictionary["txn_generation"] as! Int
    162         } else {
    163             txn = ndb_txn()
    164             guard !ndb.is_closed else { return nil }
    165             generation = ndb.generation
    166             #if TXNDEBUG
    167             txn_count += 1
    168             #endif
    169             let ok = ndb_begin_query(ndb.ndb.ndb, &txn) != 0
    170             if !ok {
    171                 return nil
    172             }
    173             generation = ndb.generation
    174             Thread.current.threadDictionary["ndb_txn"] = txn
    175             Thread.current.threadDictionary["txn_generation"] = ndb.generation
    176             inherited = false
    177         }
    178         #if TXNDEBUG
    179         print("txn: open  gen\(self.generation) '\(self.name)' \(txn_count)")
    180         #endif
    181         let moved = false
    182         let placeholderTxn = PlaceholderNdbTxn(txn: txn)
    183         guard let val = valueGetter(placeholderTxn) else { return nil }
    184         return SafeNdbTxn<T>(ndb: ndb, txn: txn, val: val, generation: generation, inherited: inherited, name: name)
    185     }
    186 
    187     private init(ndb: Ndb, txn: ndb_txn, val: consuming T, generation: Int, inherited: Bool, name: String) {
    188         self.txn = txn
    189         self.val = consume val
    190         self.moved = false
    191         self.inherited = inherited
    192         self.ndb = ndb
    193         self.generation = generation
    194         self.name = name
    195     }
    196 
    197     deinit {
    198         if self.generation != ndb.generation {
    199             print("txn: OLD GENERATION (\(self.generation) != \(ndb.generation)), IGNORING")
    200             return
    201         }
    202         if inherited {
    203             print("txn: not closing. inherited ")
    204             return
    205         }
    206         if moved {
    207             //print("txn: not closing. moved")
    208             return
    209         }
    210         if ndb.is_closed {
    211             print("txn: not closing. db closed")
    212             return
    213         }
    214 
    215         #if TXNDEBUG
    216         txn_count -= 1;
    217         print("txn: close gen\(generation) '\(name)' \(txn_count)")
    218         #endif
    219         ndb_end_query(&self.txn)
    220         //self.skip_close = true
    221         Thread.current.threadDictionary.removeObject(forKey: "ndb_txn")
    222     }
    223 
    224     // functor
    225     func map<Y>(_ transform: (borrowing T) -> Y) -> SafeNdbTxn<Y> {
    226         self.moved = true
    227         return .init(ndb: self.ndb, txn: self.txn, val: transform(val), generation: generation, inherited: inherited, name: self.name)
    228     }
    229 
    230     // comonad!?
    231     // useful for moving ownership of a transaction to another value
    232     func extend<Y>(_ with: (SafeNdbTxn<T>) -> Y) -> SafeNdbTxn<Y> {
    233         self.moved = true
    234         return .init(ndb: self.ndb, txn: self.txn, val: with(self), generation: generation, inherited: inherited, name: self.name)
    235     }
    236     
    237     consuming func maybeExtend<Y>(_ with: (consuming SafeNdbTxn<T>) -> Y?) -> SafeNdbTxn<Y>? where Y: ~Copyable {
    238         self.moved = true
    239         let ndb = self.ndb
    240         let txn = self.txn
    241         let generation = self.generation
    242         let inherited = self.inherited
    243         let name = self.name
    244         guard let newVal = with(consume self) else { return nil }
    245         return .init(ndb: ndb, txn: txn, val: newVal, generation: generation, inherited: inherited, name: name)
    246     }
    247 }
    248 
    249 protocol OptionalType {
    250     associatedtype Wrapped
    251     var optional: Wrapped? { get }
    252 }
    253 
    254 extension Optional: OptionalType {
    255     typealias Wrapped = Wrapped
    256 
    257     var optional: Wrapped? {
    258         return self
    259     }
    260 }
    261 
    262 extension NdbTxn where T: OptionalType {
    263     func collect() -> NdbTxn<T.Wrapped>? {
    264         guard let unwrappedVal: T.Wrapped = val.optional else {
    265             return nil
    266         }
    267         self.moved = true
    268         return NdbTxn<T.Wrapped>(ndb: self.ndb, txn: self.txn, val: unwrappedVal, generation: generation, inherited: inherited, name: name)
    269     }
    270 }
    271 
    272 extension NdbTxn where T == Bool { var value: T { return self.unsafeUnownedValue } }
    273 extension NdbTxn where T == Bool? { var value: T { return self.unsafeUnownedValue } }
    274 extension NdbTxn where T == Int { var value: T { return self.unsafeUnownedValue } }
    275 extension NdbTxn where T == Int? { var value: T { return self.unsafeUnownedValue } }
    276 extension NdbTxn where T == Double { var value: T { return self.unsafeUnownedValue } }
    277 extension NdbTxn where T == Double? { var value: T { return self.unsafeUnownedValue } }
    278 extension NdbTxn where T == UInt64 { var value: T { return self.unsafeUnownedValue } }
    279 extension NdbTxn where T == UInt64? { var value: T { return self.unsafeUnownedValue } }
    280 extension NdbTxn where T == String { var value: T { return self.unsafeUnownedValue } }
    281 extension NdbTxn where T == String? { var value: T { return self.unsafeUnownedValue } }
    282 extension NdbTxn where T == NoteId? { var value: T { return self.unsafeUnownedValue } }
    283 extension NdbTxn where T == NoteId { var value: T { return self.unsafeUnownedValue } }