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")
This commit is contained in:
William Casarin
2023-12-12 12:45:56 -08:00
parent 227734d286
commit bfad2ab42d
42 changed files with 126 additions and 107 deletions

View File

@@ -66,7 +66,9 @@ class EventsModel: ObservableObject {
case .auth:
break
case .eose:
let txn = NdbTxn(ndb: self.state.ndb)
guard let txn = NdbTxn(ndb: self.state.ndb) else {
return
}
load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn)
}
}

View File

@@ -83,7 +83,7 @@ class FollowersModel: ObservableObject {
case .eose(let sub_id):
if sub_id == self.sub_id {
let txn = NdbTxn(ndb: self.damus_state.ndb)
guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return }
load_profiles(relay_id: relay_id, txn: txn)
} else if sub_id == self.profiles_id {
damus_state.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])

View File

@@ -414,8 +414,10 @@ class HomeModel {
print(msg)
case .eose(let sub_id):
let txn = NdbTxn(ndb: damus_state.ndb)
guard let txn = NdbTxn(ndb: damus_state.ndb) else {
return
}
if sub_id == dms_subid {
var dms = dms.dms.flatMap { $0.events }
dms.append(contentsOf: incoming_dms)

View File

@@ -187,7 +187,7 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText
case .pubkey(let pk):
let npub = bech32_pubkey(pk)
let profile_txn = profiles.lookup(id: pk)
let profile = profile_txn.unsafeUnownedValue
let profile = profile_txn?.unsafeUnownedValue
let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
var attributedString = AttributedString(stringLiteral: "@\(disp)")
attributedString.link = URL(string: "damus:nostr:\(npub)")

View File

@@ -21,6 +21,7 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent)
guard let local_notification = generate_local_notification_object(from: ev, state: state) else {
return
}
create_local_notification(profiles: state.profiles, notify: local_notification)
}
@@ -76,7 +77,8 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu
} else if type == .like,
state.settings.like_notification,
let evid = ev.referenced_ids.last,
let liked_event = state.ndb.lookup_note(evid).unsafeUnownedValue // We are only accessing it temporarily to generate notification content
let txn = state.ndb.lookup_note(evid),
let liked_event = txn.unsafeUnownedValue // We are only accessing it temporarily to generate notification content
{
let content_preview = render_notification_content_preview(ev: liked_event, profiles: state.profiles, keypair: state.keypair)
return LocalNotification(type: .like, event: ev, target: liked_event, content: content_preview)
@@ -135,9 +137,9 @@ func render_notification_content_preview(ev: NostrEvent, profiles: Profiles, key
}
func event_author_name(profiles: Profiles, pubkey: Pubkey) -> String {
return profiles.lookup(id: pubkey).map({ profile in
Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
}).value
let profile_txn = profiles.lookup(id: pubkey)
let profile = profile_txn?.unsafeUnownedValue
return Profile.displayName(profile: profile, pubkey: pubkey).username.truncate(maxLength: 50)
}
@MainActor
@@ -173,8 +175,8 @@ func process_zap_event(state: HeadlessDamusState, ev: NostrEvent, completion: @e
return
}
guard let lnurl = state.profiles.lookup_with_timestamp(ptag)
.map({ pr in pr?.lnurl }).value else {
guard let txn = state.profiles.lookup_with_timestamp(ptag),
let lnurl = txn.map({ pr in pr?.lnurl }).value else {
completion(.failed)
return
}
@@ -221,7 +223,8 @@ func get_zap_target_pubkey(ev: NostrEvent, ndb: Ndb) -> Pubkey? {
}
// we can't trust the p tag on note zaps because they can be faked
guard let pk = ndb.lookup_note(etag).unsafeUnownedValue?.pubkey else {
guard let txn = ndb.lookup_note(etag),
let pk = txn.unsafeUnownedValue?.pubkey else {
// We don't have the event in cache so we can't check the pubkey.
// We could return this as an invalid zap but that wouldn't be correct

View File

@@ -128,7 +128,7 @@ class ProfileModel: ObservableObject, Equatable {
break
//notify(.notice, notice)
case .eose:
let txn = NdbTxn(ndb: damus.ndb)
guard let txn = NdbTxn(ndb: damus.ndb) else { return }
if resp.subid == sub_id {
load_profiles(context: "profile", profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus, txn: txn)
}

View File

@@ -83,7 +83,7 @@ class SearchHomeModel: ObservableObject {
// global events are not realtime
unsubscribe(to: relay_id)
let txn = NdbTxn(ndb: damus_state.ndb)
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
}

View File

@@ -80,7 +80,7 @@ class SearchModel: ObservableObject {
self.loading = false
if sub_id == self.sub_id {
let txn = NdbTxn(ndb: state.ndb)
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn)
}
}

View File

@@ -120,7 +120,7 @@ class ThreadModel: ObservableObject {
}
if sub_id == self.base_subid {
let txn = NdbTxn(ndb: damus_state.ndb)
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "thread", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map)), damus_state: damus_state, txn: txn)
}
}

View File

@@ -55,7 +55,7 @@ class ZapsModel: ObservableObject {
break
case .eose:
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
let txn = NdbTxn(ndb: state.ndb)
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "zaps_model", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn)
case .event(_, let ev):
guard ev.kind == 9735,