Add sync mechanism to prevent background crashes and fix ndb reopen order
This adds a sync mechanism in Ndb.swift to coordinate certain usage of nostrdb.c calls and the need to close nostrdb due to app lifecycle requirements. Furthermore, it fixes the order of operations when re-opening NostrDB, to avoid race conditions where a query uses an older Ndb generation. This sync mechanism allows multiple queries to happen simultaneously (from the Swift-side), while preventing ndb from simultaneously closing during such usages. It also does that while keeping the Ndb interface sync and nonisolated, which keeps the API easy to use from Swift/SwiftUI and allows for parallel operations to occur. If Swift Actors were to be used (e.g. creating an NdbActor), the Ndb.swift interface would change in such a way that it would propagate the need for several changes throughout the codebase, including loading logic in some ViewModels. Furthermore, it would likely decrease performance by forcing Ndb.swift operations to run sequentially when they could run in parallel. Changelog-Fixed: Fixed crashes that happened when the app went into background mode Closes: https://github.com/damus-io/damus/issues/3245 Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -92,11 +92,11 @@ extension NostrNetworkManager {
|
||||
|
||||
private func publishProfileUpdates(metadataEvent: borrowing UnownedNdbNote) {
|
||||
let now = UInt64(Date.now.timeIntervalSince1970)
|
||||
ndb.write_profile_last_fetched(pubkey: metadataEvent.pubkey, fetched_at: now)
|
||||
try? ndb.write_profile_last_fetched(pubkey: metadataEvent.pubkey, fetched_at: now)
|
||||
|
||||
if let relevantStreams = streams[metadataEvent.pubkey] {
|
||||
// If we have the user metadata event in ndb, then we should have the profile record as well.
|
||||
guard let profile = ndb.lookup_profile_and_copy(metadataEvent.pubkey) else { return }
|
||||
guard let profile = try? ndb.lookup_profile_and_copy(metadataEvent.pubkey) else { return }
|
||||
for relevantStream in relevantStreams.values {
|
||||
relevantStream.continuation.yield(profile)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ extension NostrNetworkManager {
|
||||
/// This is useful for local profile changes (e.g., nip05 validation, donation percentage updates)
|
||||
func notifyProfileUpdate(pubkey: Pubkey) {
|
||||
if let relevantStreams = streams[pubkey] {
|
||||
guard let profile = ndb.lookup_profile_and_copy(pubkey) else { return }
|
||||
guard let profile = try? ndb.lookup_profile_and_copy(pubkey) else { return }
|
||||
for relevantStream in relevantStreams.values {
|
||||
relevantStream.continuation.yield(profile)
|
||||
}
|
||||
@@ -141,7 +141,7 @@ extension NostrNetworkManager {
|
||||
|
||||
// Yield cached profile immediately so views don't flash placeholder content.
|
||||
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
||||
if yieldCached, let existingProfile = ndb.lookup_profile_and_copy(pubkey) {
|
||||
if yieldCached, let existingProfile = try? ndb.lookup_profile_and_copy(pubkey) {
|
||||
continuation.yield(existingProfile)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ extension NostrNetworkManager {
|
||||
// Callers that only need updates (not initial state) can opt out via yieldCached: false.
|
||||
if yieldCached {
|
||||
for pubkey in pubkeys {
|
||||
if let existingProfile = ndb.lookup_profile_and_copy(pubkey) {
|
||||
if let existingProfile = try? ndb.lookup_profile_and_copy(pubkey) {
|
||||
continuation.yield(existingProfile)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ extension NostrNetworkManager {
|
||||
let filter = NostrFilter(ids: [noteId], limit: 1)
|
||||
|
||||
// Since note ids point to immutable objects, we can do a simple ndb lookup first
|
||||
if let noteKey = self.ndb.lookup_note_key(noteId) {
|
||||
if let noteKey = try? self.ndb.lookup_note_key(noteId) {
|
||||
return NdbNoteLender(ndb: self.ndb, noteKey: noteKey)
|
||||
}
|
||||
|
||||
@@ -413,18 +413,18 @@ extension NostrNetworkManager {
|
||||
|
||||
switch query {
|
||||
case .profile(let pubkey):
|
||||
let profileNotNil = self.ndb.lookup_profile(pubkey, borrow: { pr in
|
||||
let profileNotNil = try? self.ndb.lookup_profile(pubkey, borrow: { pr in
|
||||
switch pr {
|
||||
case .some(let pr): return pr.profile != nil
|
||||
case .none: return true
|
||||
}
|
||||
})
|
||||
if profileNotNil {
|
||||
if profileNotNil ?? false {
|
||||
return .profile(pubkey)
|
||||
}
|
||||
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
||||
case .event(let evid):
|
||||
if let event = self.ndb.lookup_note_and_copy(evid) {
|
||||
if let event = try? self.ndb.lookup_note_and_copy(evid) {
|
||||
return .event(event)
|
||||
}
|
||||
filter = NostrFilter(ids: [evid], limit: 1)
|
||||
|
||||
@@ -87,7 +87,7 @@ extension NostrNetworkManager {
|
||||
private func getLatestNIP65RelayListEvent() -> NdbNote? {
|
||||
guard let latestRelayListEventId = delegate.latestRelayListEventIdHex else { return nil }
|
||||
guard let latestRelayListEventId = NoteId(hex: latestRelayListEventId) else { return nil }
|
||||
return delegate.ndb.lookup_note_and_copy(latestRelayListEventId)
|
||||
return try? delegate.ndb.lookup_note_and_copy(latestRelayListEventId)
|
||||
}
|
||||
|
||||
/// Gets the latest `kind:3` relay list from NostrDB.
|
||||
|
||||
@@ -74,12 +74,12 @@ class Profiles {
|
||||
profile_data(pubkey).zapper
|
||||
}
|
||||
|
||||
func lookup_with_timestamp<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
||||
func lookup_with_timestamp<T>(_ pubkey: Pubkey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||
return try ndb.lookup_profile(pubkey, borrow: lendingFunction)
|
||||
}
|
||||
|
||||
func lookup_lnurl(_ pubkey: Pubkey) -> String? {
|
||||
return lookup_with_timestamp(pubkey, borrow: { pr in
|
||||
func lookup_lnurl(_ pubkey: Pubkey) throws -> String? {
|
||||
return try lookup_with_timestamp(pubkey, borrow: { pr in
|
||||
switch pr {
|
||||
case .some(let pr): return pr.lnurl
|
||||
case .none: return nil
|
||||
@@ -87,16 +87,16 @@ class Profiles {
|
||||
})
|
||||
}
|
||||
|
||||
func lookup_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) rethrows -> T {
|
||||
func lookup_by_key<T>(key: ProfileKey, borrow lendingFunction: (_: borrowing ProfileRecord?) throws -> T) throws -> T {
|
||||
return try ndb.lookup_profile_by_key(key: key, borrow: lendingFunction)
|
||||
}
|
||||
|
||||
func search(_ query: String, limit: Int) -> [Pubkey] {
|
||||
ndb.search_profile(query, limit: limit)
|
||||
func search(_ query: String, limit: Int) throws -> [Pubkey] {
|
||||
try ndb.search_profile(query, limit: limit)
|
||||
}
|
||||
|
||||
func lookup(id: Pubkey) -> Profile? {
|
||||
return ndb.lookup_profile(id, borrow: { pr in
|
||||
func lookup(id: Pubkey) throws -> Profile? {
|
||||
return try ndb.lookup_profile(id, borrow: { pr in
|
||||
switch pr {
|
||||
case .none:
|
||||
return nil
|
||||
@@ -107,12 +107,12 @@ class Profiles {
|
||||
})
|
||||
}
|
||||
|
||||
func lookup_key_by_pubkey(_ pubkey: Pubkey) -> ProfileKey? {
|
||||
ndb.lookup_profile_key(pubkey)
|
||||
func lookup_key_by_pubkey(_ pubkey: Pubkey) throws -> ProfileKey? {
|
||||
try ndb.lookup_profile_key(pubkey)
|
||||
}
|
||||
|
||||
func has_fresh_profile(id: Pubkey) -> Bool {
|
||||
guard let fetched_at = ndb.read_profile_last_fetched(pubkey: id)
|
||||
func has_fresh_profile(id: Pubkey) throws -> Bool {
|
||||
guard let fetched_at = try ndb.read_profile_last_fetched(pubkey: id)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user