Commit Graph

17 Commits

Author SHA1 Message Date
Daniel D’Aquino
20dc672dbf 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>
2025-12-29 11:01:23 -08:00
alltheseas
a0cecdc8ad Fix missing profile names and pictures due to stream timing
When a view subscribes to profile updates via streamProfile() or
streamProfiles(), the stream now immediately yields any existing
profile data from NostrDB before waiting for network updates.

Previously, subscribers had to wait up to ~1 second for the
subscriptionSwitcherTask to restart the profile listener before
receiving any data. During this window, views would display
abbreviated pubkeys (e.g., "npub1abc...") or robohash placeholders
instead of the cached profile name and picture.

The fix adds a simple NDB lookup when creating the stream. This has
negligible performance impact since:
- It's a one-time operation per subscription (not per update)
- The same lookup was already happening in view bodies anyway
- NDB lookups are fast local queries

A new `yieldCached` parameter (default: true) allows callers to opt
out of the initial cached emission. NoteContentView uses this to
avoid redundant artifact re-renders — it only needs network updates
since its initial render already uses cached profile data.

Furthermore, when a profile has no metadata, the display name now shows
"npub1yrse...q9ye" instead of "1yrsedhw:8q0pq9ye" for a better UX.

Closes: https://github.com/damus-io/damus/issues/3454
Closes: https://github.com/damus-io/damus/issues/3455
Changelog-Changed: Changed abbreviated pubkey format to npub1...xyz for better readability
Changelog-Fixed: Fixed instances where a profile would not display profile name and picture for a few seconds
Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
Co-authored-by: Daniel D’Aquino <daniel@daquino.me>
2025-12-19 17:21:49 -08:00
Daniel D’Aquino
f844ed9931 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>
2025-12-07 11:02:45 -08:00
Daniel D’Aquino
52115d07c2 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>
2025-11-21 14:59:00 -08:00
Daniel D’Aquino
58e6a49bcf Fix race condition leading to intermittent issues with ndb streaming and related tests
A race condition was identified where notes would get dropped if they
get indexed in the time window between when a query is made and the subscription is made.

The issue was fixed by making the subscribe call before making the query
call, to ensure we get all notes from that time when we perform the
query.

This dropped the failure rate for ndb subscription tests from about 20%
down to about 4%.

Local relay model issue was not publicly released, which is why the
changelog entry is "none".

Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-24 18:40:32 -07:00
Daniel D’Aquino
7cf9a07099 Add more automated tests around ndb streaming
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-24 17:15:04 -07:00
Daniel D’Aquino
7afcaa99fe Reduce race condition probability in Ndb streaming functions
This attempts to reduce race conditions coming from Ndb streaming
functions that could lead to lost notes or crashes.

It does so by making two improvements:
1. Instead of callbacks, now the callback handler uses async streams,
   which reduces the chances of a callback being called before the last
   item was processed by the consumer.
2. The callback handler will now queue up received notes if there are
   no listeners yet. This is helpful because we need to issue the
   subscribe call to nostrdb before getting the subscription id and
   setting up a listener, but in between that time nostrdb may still
   send notes which would effectively get dropped without this queuing
   mechanism.

Changelog-Fixed: Improved robustness in the part of the code that streams notes from nostrdb
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-24 16:25:44 -07:00
Daniel D’Aquino
9555145359 Fix automated test issues
Closes: https://github.com/damus-io/damus/issues/3275
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-20 17:35:23 -07:00
Daniel D’Aquino
ab2c16288b Fix test compilation issues
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-13 15:16:54 -07:00
Daniel D’Aquino
01ec05ab32 Fix test build error
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-10-06 11:33:45 -07:00
Daniel D’Aquino
de528f3f70 Improve loading speed on home timeline
This commit improves the loading speed for the home timeline (and likely
other areas of the app) by employing various techniques and changes:
- Network EOSE timeout reduced from 10 seconds down to 5 seconds
- Network EOSE does not wait on relays with broken connections
- Offload HomeModel handler event processing to separate tasks to
  avoid a large backlog
- Give SubscriptionManager streamers more fine-grained EOSE signals for
  local optimization
- Only wait for Ndb EOSE on the home timeline for faster loading
- Add logging with time elapsed measurements for easier identification of
  loading problems

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:10 -07:00
Daniel D’Aquino
2185984ed7 Stream from both NDB and network relays
This commit takes a step back from the full local relay model by
treating NostrDB as one of the many relays streamed from, instead of the
one exclusive relay that other classes rely on.

This was done to reduce regression risk from the local relay model
migration, without discarding the migration work already done.

The full "local relay model" behavior (exclusive NDB streaming) was
hidden behind a feature flag for easy migration later on.

Closes: https://github.com/damus-io/damus/issues/3225
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:10 -07:00
Daniel D’Aquino
3290e1f9d2 Improve NostrNetworkManager interfaces
This commit improves NostrNetworkManager interfaces to be easier to use,
and with more options on how to read data from the Nostr network

This reduces the amount of duplicate logic in handling streams, and also
prevents possible common mistakes when using the standard subscribe method.

This fixes an issue with the mute list manager (which prompted for this
interface improvement, as the root cause is similar to other similar
issues).

Closes: https://github.com/damus-io/damus/issues/3221
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00
Daniel D’Aquino
c4c3656f90 Multi-session subscriptions and RelayPool reopening
This commit implements nostr network subscriptions that survive between
sessions, as well as improved handling of RelayPool opening/closing with
respect to the app lifecycle.

This prevents stale data after users swap out and back into Damus.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00
Daniel D’Aquino
739a3a0b8c Add more test cases to SubscriptionManager tests
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00
Daniel D’Aquino
ab6ea7a9c1 Fix issue where repost and like counts would not appear
Previously, HomeModel could listen to all subscriptions throughout the
app, and it would handle reaction and repost counting.

Once moved to the local relay model, HomeModel no longer had access to
all subscriptions, causing those counts to disappear.

The issue was fixed by doing the counting from ThreadModel itself, which
better isolates concerns throughout the app.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00
Daniel D’Aquino
940b83f5c4 Add ndb subscription tests
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:02 -07:00