Redesign Ndb.swift interface with build safety
This commit redesigns the Ndb.swift interface with a focus on build-time safety against crashes. It removes the external usage of NdbTxn and SafeNdbTxn, restricting it to be used only in NostrDB internal code. This prevents dangerous and crash prone usages throughout the app, such as holding transactions in a variable in an async function (which can cause thread-based reference counting to incorrectly deinit inherited transactions in use by separate callers), as well as holding unsafe unowned values longer than the lifetime of their corresponding transactions. Closes: https://github.com/damus-io/damus/issues/3364 Changelog-Fixed: Fixed several crashes throughout the app Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -110,29 +110,29 @@ class SearchHomeModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch<Y>(profiles: Profiles, load: PubkeysToLoad, cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||
func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad, cache: EventCache) -> [Pubkey] {
|
||||
switch load {
|
||||
case .from_events(let events):
|
||||
return find_profiles_to_fetch_from_events(profiles: profiles, events: events, cache: cache, txn: txn)
|
||||
return find_profiles_to_fetch_from_events(profiles: profiles, events: events, cache: cache)
|
||||
case .from_keys(let pks):
|
||||
return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks, txn: txn)
|
||||
return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks)
|
||||
}
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch_from_keys<Y>(profiles: Profiles, pks: [Pubkey], txn: NdbTxn<Y>) -> [Pubkey] {
|
||||
Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk, txn: txn) }))
|
||||
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [Pubkey]) -> [Pubkey] {
|
||||
Array(Set(pks.filter { pk in !profiles.has_fresh_profile(id: pk) }))
|
||||
}
|
||||
|
||||
func find_profiles_to_fetch_from_events<Y>(profiles: Profiles, events: [NostrEvent], cache: EventCache, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent], cache: EventCache) -> [Pubkey] {
|
||||
var pubkeys = Set<Pubkey>()
|
||||
|
||||
for ev in events {
|
||||
// lookup profiles from boosted events
|
||||
if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey, txn: txn) {
|
||||
if ev.known_kind == .boost, let bev = ev.get_inner_event(cache: cache), !profiles.has_fresh_profile(id: bev.pubkey) {
|
||||
pubkeys.insert(bev.pubkey)
|
||||
}
|
||||
|
||||
if !profiles.has_fresh_profile(id: ev.pubkey, txn: txn) {
|
||||
if !profiles.has_fresh_profile(id: ev.pubkey) {
|
||||
pubkeys.insert(ev.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +31,18 @@ struct PullDownSearchView: View {
|
||||
}
|
||||
|
||||
do {
|
||||
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
||||
for note_key in note_keys {
|
||||
guard let note = state.ndb.lookup_note_by_key_with_txn(note_key, txn: txn) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if !keyset.contains(note_key) {
|
||||
let owned_note = note.to_owned()
|
||||
res.append(owned_note)
|
||||
keyset.insert(note_key)
|
||||
}
|
||||
state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
||||
switch maybeUnownedNote {
|
||||
case .none: return // Skip this
|
||||
case .some(let unownedNote):
|
||||
if !keyset.contains(note_key) {
|
||||
let owned_note = unownedNote.toOwned()
|
||||
res.append(owned_note)
|
||||
keyset.insert(note_key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,17 +154,18 @@ struct SearchResultsView: View {
|
||||
}
|
||||
|
||||
do {
|
||||
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
||||
for note_key in note_keys {
|
||||
guard let note = damus_state.ndb.lookup_note_by_key_with_txn(note_key, txn: txn) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if !keyset.contains(note_key) {
|
||||
let owned_note = note.to_owned()
|
||||
res.append(owned_note)
|
||||
keyset.insert(note_key)
|
||||
}
|
||||
damus_state.ndb.lookup_note_by_key(note_key, borrow: { maybeUnownedNote in
|
||||
switch maybeUnownedNote {
|
||||
case .none: return
|
||||
case .some(let unownedNote):
|
||||
if !keyset.contains(note_key) {
|
||||
let owned_note = unownedNote.toOwned()
|
||||
res.append(owned_note)
|
||||
keyset.insert(note_key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +183,10 @@ struct SearchResultsView: View {
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.onAppear {
|
||||
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
|
||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search)
|
||||
}
|
||||
.onChange(of: search) { new in
|
||||
guard let txn = NdbTxn.init(ndb: damus_state.ndb) else { return }
|
||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search, txn: txn)
|
||||
self.result = search_for_string(profiles: damus_state.profiles, contacts: damus_state.contacts, search: search)
|
||||
}
|
||||
.onChange(of: search) { query in
|
||||
debouncer.debounce {
|
||||
@@ -208,7 +207,7 @@ struct SearchResultsView_Previews: PreviewProvider {
|
||||
*/
|
||||
|
||||
|
||||
func search_for_string<Y>(profiles: Profiles, contacts: Contacts, search new: String, txn: NdbTxn<Y>) -> Search? {
|
||||
func search_for_string(profiles: Profiles, contacts: Contacts, search new: String) -> Search? {
|
||||
guard new.count != 0 else {
|
||||
return nil
|
||||
}
|
||||
@@ -251,7 +250,7 @@ func search_for_string<Y>(profiles: Profiles, contacts: Contacts, search new: St
|
||||
return .naddr(naddr)
|
||||
}
|
||||
|
||||
let multisearch = MultiSearch(text: new, hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, contacts: contacts, search: new, txn: txn))
|
||||
let multisearch = MultiSearch(text: new, hashtag: make_hashtagable(searchQuery), profiles: search_profiles(profiles: profiles, contacts: contacts, search: new))
|
||||
return .multi(multisearch)
|
||||
}
|
||||
|
||||
@@ -268,7 +267,7 @@ func make_hashtagable(_ str: String) -> String {
|
||||
return String(new.filter{$0 != " "})
|
||||
}
|
||||
|
||||
func search_profiles<Y>(profiles: Profiles, contacts: Contacts, search: String, txn: NdbTxn<Y>) -> [Pubkey] {
|
||||
func search_profiles(profiles: Profiles, contacts: Contacts, search: String) -> [Pubkey] {
|
||||
// Search by hex pubkey.
|
||||
if let pubkey = hex_decode_pubkey(search),
|
||||
profiles.lookup_key_by_pubkey(pubkey) != nil
|
||||
@@ -285,7 +284,7 @@ func search_profiles<Y>(profiles: Profiles, contacts: Contacts, search: String,
|
||||
return [pk]
|
||||
}
|
||||
|
||||
return profiles.search(search, limit: 128, txn: txn).sorted { a, b in
|
||||
return profiles.search(search, limit: 128).sorted { a, b in
|
||||
let aFriendTypePriority = get_friend_type(contacts: contacts, pubkey: a)?.priority ?? 0
|
||||
let bFriendTypePriority = get_friend_type(contacts: contacts, pubkey: b)?.priority ?? 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user