Fix profile crash

This fixes a crash that would occasionally occur when visiting profiles.

NdbTxn objects were being deinitialized on different threads from their
initialization, causing incorrect reference count decrements in thread-local
transaction dictionaries. This led to premature destruction of shared ndb_txn
C objects still in use by other tasks, resulting in use-after-free crashes.

The root cause is that Swift does not guarantee tasks resume on the same
thread after await suspension points, while NdbTxn's init/deinit rely on
thread-local storage to track inherited transaction reference counts.

This means that `NdbTxn` objects cannot be used in async functions, as
that may cause the garbage collector to deinitialize `NdbTxn` at the end
of such function, which may be running on a different thread at that
point, causing the issue explained above.

The fix in this case is to eliminate the `async` version of the
`NdbNoteLender.borrow` method, and update usages to utilize other
available methods.

Note: This is a rewrite of the fix in https://github.com/damus-io/damus/pull/3329

Note 2: This relates to the fix of an unreleased feature, so therefore no
changelog is needed.

Changelog-None
Co-authored-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
Closes: https://github.com/damus-io/damus/issues/3327
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2025-11-19 20:14:34 -08:00
parent d651084465
commit 52115d07c2
4 changed files with 94 additions and 29 deletions

View File

@@ -135,13 +135,12 @@ extension NostrNetworkManager {
let filter = NostrFilter(kinds: [.relay_list], authors: [delegate.keypair.pubkey])
for await noteLender in self.reader.streamIndefinitely(filters: [filter]) {
let currentRelayListCreationDate = self.getUserCurrentRelayListCreationDate()
try? await noteLender.borrow({ note in
guard note.pubkey == self.delegate.keypair.pubkey else { return } // Ensure this new list was ours
guard note.createdAt > (currentRelayListCreationDate ?? 0) else { return } // Ensure this is a newer list
guard let relayList = try? NIP65.RelayList(event: note) else { return } // Ensure it is a valid NIP-65 list
try? await self.set(userRelayList: relayList) // Set the validated list
})
guard let note = noteLender.justGetACopy() else { continue }
guard note.pubkey == self.delegate.keypair.pubkey else { continue } // Ensure this new list was ours
guard note.created_at > (currentRelayListCreationDate ?? 0) else { continue } // Ensure this is a newer list
guard let relayList = try? NIP65.RelayList(event: note) else { continue } // Ensure it is a valid NIP-65 list
try? await self.set(userRelayList: relayList) // Set the validated list
}
}