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 } }