1
0
mirror of git://jb55.com/damus synced 2024-09-29 00:10:43 +00:00
damus/nostrdb/NdbTxn.swift
William Casarin bfad2ab42d ndb/txn: make transactions failable
Since there may be situations where we close and re-open the database,
we need to make sure transactions fail when the database is not open.

Make NdbTxn an init?() constructor and check for ndb.closed. If it's
closed, then fail transaction construction.

This fixes crashes during high database activity when switching from
background to foreground and vice-versa.

Fixes: da2bdad18d ("nostrdb: close database when backgrounded")
2024-01-10 14:27:02 -08:00

120 lines
3.6 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 () }) {
guard !ndb.closed else { return nil }
#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 ok = ndb_begin_query(ndb.ndb.ndb, &self.txn) != 0
if !ok {
return nil
}
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 } }