fix(nests): prune stale presence entries from LiveActivitiesChannel

Presence entries are valid for ~10 min (PRESENCE_FRESHNESS_WINDOW_SECONDS in
NestsFeedFilter). Without explicit cleanup, presenceNotes would grow unbounded:
every author who ever heartbeats in a room leaves an entry there forever.
The freshness check kept the filter result correct, but memory only ever grew.

Add LiveActivitiesChannel.pruneStalePresence(cutoff) and call it from
LocalCache.pruneOldMessagesChannel alongside the existing notes prune. Cutoff is
2x the freshness window (20 min) so a presence still inside any feed's window
can never be reaped.
This commit is contained in:
Claude
2026-04-30 14:09:30 +00:00
parent 566133750b
commit 612f2aa31e
2 changed files with 34 additions and 0 deletions
@@ -80,6 +80,27 @@ class LiveActivitiesChannel(
flowSet?.notes?.invalidateData()
}
/**
* Drop presence entries older than [cutoff]. Without this, every
* author who ever heartbeats in this room would leave an entry
* here forever, even though the freshness check in
* `NestsFeedFilter` already treats anything past
* `PRESENCE_FRESHNESS_WINDOW_SECONDS` (10 min) as dead. Run on the
* same schedule as [pruneOldMessages] with a generous cutoff (2×
* the freshness window) so a presence still inside any feed's
* window can never be pruned.
*/
fun pruneStalePresence(cutoff: Long): Int {
val toRemove = mutableListOf<HexKey>()
presenceNotes.forEach { author, note ->
val createdAt = note.event?.createdAt
if (createdAt == null || createdAt < cutoff) toRemove.add(author)
}
toRemove.forEach { presenceNotes.remove(it) }
if (toRemove.isNotEmpty()) flowSet?.notes?.invalidateData()
return toRemove.size
}
fun address() = address
override fun relays() = info?.allRelayUrls()?.toSet()?.ifEmpty { null } ?: super.relays()