mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-07-22 07:48:27 +00:00
perf: reduce background and mobile-data battery usage
Five targeted fixes for the biggest idle drains: - NostrClient keep-alive sweep: the 60s reconnect loop kept ticking for the whole life of the process even after disconnect() (i.e. the entire time the app sat in the background). isActive is now a StateFlow and the loop suspends on it, so an inactive client schedules zero timers. - Relay WebSocket pings on cellular: every ping on an idle connection promotes the radio out of its low-power state, per connection. The mobile-data client now pings every 240s instead of 120s (wifi keeps 120s), staying under common carrier NAT idle timeouts. - Always-on service watchdog: the 5-min health-check alarm no longer uses the _WAKEUP variant. Pulling the CPU out of sleep to restart a service that can't do useful network work on a sleeping device was pure cost; the alarm now fires as soon as the device is next awake, and the WorkManager + FCM/UnifiedPush layers still cover doze. - ScheduledPostWorker: the 15-min periodic worker was enqueued forever for every user, waking (often cold-starting) the process with nothing to publish. It is now enqueued exactly while the store holds a PENDING post, driven by a store-flow observer in AppModules. - CalendarReminderWorker: same unconditional 15-min periodic schedule, but worse — LocalCache is memory-only, so a WorkManager wake of a dead process can never find an RSVP to remind about. The worker is now scheduled when an ACCEPTED RSVP lands in LocalCache and cancels its own chain when nothing upcoming remains (or the feature is disabled). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016RJ8EAsdkHx5WHHU2eQJ1P
This commit is contained in:
@@ -158,13 +158,17 @@ Google Play Services infrastructure.
|
||||
|
||||
### Layer 4: AlarmManager Watchdog (5 minutes)
|
||||
|
||||
**What:** `ServiceWatchdogManager` fires an `ELAPSED_REALTIME_WAKEUP` alarm every 5
|
||||
**What:** `ServiceWatchdogManager` fires an `ELAPSED_REALTIME` alarm every 5
|
||||
minutes. The receiver checks if the service should be running and restarts it.
|
||||
|
||||
**Why needed:** This is the "belt and suspenders" layer. If all of the above layers fail
|
||||
(sticky restart blocked, alarm from `onTaskRemoved` didn't fire, broadcast wasn't
|
||||
delivered), the watchdog will catch it within 5 minutes. Uses `ELAPSED_REALTIME_WAKEUP` to
|
||||
wake the device from sleep, ensuring the check happens even in Doze.
|
||||
delivered), the watchdog will catch it within 5 minutes of the device being awake.
|
||||
The alarm deliberately does NOT use the `_WAKEUP` variant: pulling the CPU out of
|
||||
sleep every 5 minutes is a battery cost with no payoff, because a service restarted
|
||||
on a sleeping device can't do useful network work until the device wakes anyway.
|
||||
While the device sleeps, Layer 5 (WorkManager) and Layer 8 (FCM/UnifiedPush) cover
|
||||
delivery; the moment the device wakes, the pending watchdog alarm fires.
|
||||
|
||||
### Layer 5: WorkManager Periodic Catch-Up (15 minutes)
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
||||
import com.vitorpamplona.amethyst.napplet.DataStoreNappletPermissionStore
|
||||
import com.vitorpamplona.amethyst.napplet.DataStoreNostrSignerPermissionStore
|
||||
import com.vitorpamplona.amethyst.service.CachedRichTextParser
|
||||
import com.vitorpamplona.amethyst.service.calendar.CalendarReminderWorker
|
||||
import com.vitorpamplona.amethyst.service.cast.CastRegistry
|
||||
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager
|
||||
import com.vitorpamplona.amethyst.service.connectivity.ConnectivityStatus
|
||||
@@ -86,6 +87,7 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFind
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.speedLogger.RelaySpeedLogger
|
||||
import com.vitorpamplona.amethyst.service.safeCacheDir
|
||||
import com.vitorpamplona.amethyst.service.scheduledposts.ScheduledPostStatus
|
||||
import com.vitorpamplona.amethyst.service.scheduledposts.ScheduledPostStore
|
||||
import com.vitorpamplona.amethyst.service.scheduledposts.ScheduledPostWorker
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.BlossomServerResolver
|
||||
@@ -105,6 +107,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineT
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.stats.RelayReqStats
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDecoder
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.SurgeDns
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.SurgeDnsStore
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
|
||||
@@ -124,6 +127,8 @@ import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcClie
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.TOR_ELECTRUMX_SERVERS
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.nip52Calendar.appt.tags.RSVPStatusTag
|
||||
import com.vitorpamplona.quartz.nip52Calendar.rsvp.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.CachingOnchainBackend
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.EsploraBackend
|
||||
@@ -887,17 +892,48 @@ class AppModules(
|
||||
// starts observing LocalCache for notification-worthy events
|
||||
notificationDispatcher.start()
|
||||
|
||||
// Schedule the scheduled-posts worker (periodic + one-time catch-up).
|
||||
// Runs independently of the always-on notification setting so scheduled
|
||||
// posts still fire when always-on notifications are disabled.
|
||||
// Keep the scheduled-posts worker (15-min periodic + one-time catch-up)
|
||||
// enqueued exactly while the store holds a PENDING post. An always-on
|
||||
// periodic worker wakes — and often cold-starts — the whole process every
|
||||
// 15 minutes forever, even for users who never schedule a post. The store
|
||||
// is durable (JSON on disk) and the single source of truth, so the worker
|
||||
// is (re-)enqueued from any mutation that produces a PENDING row and
|
||||
// cancelled when the last one drains. Runs independently of the always-on
|
||||
// notification setting so scheduled posts still fire when always-on
|
||||
// notifications are disabled.
|
||||
applicationIOScope.launch {
|
||||
// Force the initial disk load; the flow's initial value is an empty
|
||||
// list until the store is first touched.
|
||||
scheduledPostStore.list()
|
||||
scheduledPostStore.flow
|
||||
.map { posts -> posts.any { it.status == ScheduledPostStatus.PENDING } }
|
||||
.distinctUntilChanged()
|
||||
.collect { hasPending ->
|
||||
if (hasPending) {
|
||||
ScheduledPostWorker.schedule(appContext)
|
||||
ScheduledPostWorker.scheduleCatchUp(appContext)
|
||||
} else {
|
||||
ScheduledPostWorker.cancelPeriodic(appContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic scan that posts "starting soon" notifications for NIP-52 appointments the
|
||||
// user has RSVP'd to as ACCEPTED. 15-minute cadence matches both the WorkManager
|
||||
// periodic minimum and the lead-time window.
|
||||
com.vitorpamplona.amethyst.service.calendar.CalendarReminderWorker
|
||||
.schedule(appContext)
|
||||
// "Starting soon" reminders for NIP-52 appointments the user RSVP'd to as
|
||||
// ACCEPTED. The 15-min periodic scanner is only scheduled while it can
|
||||
// plausibly fire: this observer enqueues it when an accepted RSVP lands in
|
||||
// LocalCache, and the worker cancels its own chain when the cache holds
|
||||
// nothing that could still start. LocalCache is memory-only, so the
|
||||
// unconditional schedule this replaces could never fire from a WorkManager
|
||||
// cold start anyway — it only cost battery.
|
||||
applicationIOScope.launch {
|
||||
LocalCache
|
||||
.observeNewEvents<CalendarRSVPEvent>(Filter(kinds = listOf(CalendarRSVPEvent.KIND)))
|
||||
.collect { rsvp ->
|
||||
if (rsvp.status() == RSVPStatusTag.STATUS.ACCEPTED) {
|
||||
CalendarReminderWorker.schedule(appContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for account login and start/stop always-on notification service
|
||||
applicationIOScope.launch {
|
||||
|
||||
@@ -47,6 +47,13 @@ import java.util.concurrent.TimeUnit
|
||||
* consults [CalendarReminderStore] to skip events that have already been notified for. Run as
|
||||
* a 15-minute periodic worker: that's the WorkManager minimum and matches the resolution of
|
||||
* the reminder UI ("starts in ~15 min" is the smallest interval users perceive as "soon").
|
||||
*
|
||||
* The periodic chain is only kept alive while it can plausibly fire: the ACCEPTED-RSVP
|
||||
* observer in AppModules calls [schedule] when an accepted RSVP lands in LocalCache, and
|
||||
* [doWork] cancels the chain when the cache holds no accepted RSVP that could still start.
|
||||
* LocalCache is memory-only, so a WorkManager wake of a dead process always sees an empty
|
||||
* cache and can never fire a reminder — an unconditional periodic schedule would cold-start
|
||||
* the whole app graph every 15 minutes forever for zero benefit.
|
||||
*/
|
||||
class CalendarReminderWorker(
|
||||
appContext: Context,
|
||||
@@ -55,7 +62,10 @@ class CalendarReminderWorker(
|
||||
override suspend fun doWork(): Result {
|
||||
val prefs = CalendarReminderPrefs(applicationContext)
|
||||
if (!prefs.isEnabled()) {
|
||||
Log.d(TAG) { "Reminders disabled; skipping scan." }
|
||||
Log.d(TAG) { "Reminders disabled; ending periodic chain." }
|
||||
// The settings toggle re-schedules on enable; no reason to keep
|
||||
// waking the process while the feature is off.
|
||||
cancel(applicationContext)
|
||||
return Result.success()
|
||||
}
|
||||
val now = TimeUtils.now()
|
||||
@@ -110,6 +120,27 @@ class CalendarReminderWorker(
|
||||
|
||||
// Prune entries for events that ended more than a day ago — they can't fire again.
|
||||
store.forgetBefore(now - PRUNE_AGE_SECONDS)
|
||||
|
||||
// Nothing left that could ever fire → end the periodic chain instead of
|
||||
// waking the process every 15 minutes forever. An RSVP whose target event
|
||||
// hasn't been fetched yet (start unknown) counts as "could still fire" so
|
||||
// the chain survives until the target resolves. The ACCEPTED-RSVP observer
|
||||
// in AppModules re-schedules the worker the next time a live session sees
|
||||
// an accepted RSVP.
|
||||
val couldStillFire =
|
||||
acceptedRsvps.any { rsvp ->
|
||||
val targetAddress = rsvp.calendarEventAddress() ?: return@any false
|
||||
val start =
|
||||
LocalCache.addressables
|
||||
.get(targetAddress)
|
||||
?.appointmentView()
|
||||
?.startSeconds
|
||||
start == null || start > now
|
||||
}
|
||||
if (!couldStillFire) {
|
||||
Log.d(TAG) { "No accepted RSVP can still fire; ending periodic chain." }
|
||||
cancel(applicationContext)
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,13 @@ class ServiceWatchdogManager {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
)
|
||||
|
||||
// ELAPSED_REALTIME (not _WAKEUP): a health check is not worth pulling
|
||||
// the CPU out of sleep — if the device is asleep, a restarted service
|
||||
// couldn't do useful network work anyway. The alarm fires as soon as
|
||||
// the device is next awake, and the deeper restart layers (15-min
|
||||
// WorkManager job, FCM/UnifiedPush) cover the force-stop/doze cases.
|
||||
alarmManager.setInexactRepeating(
|
||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + WATCHDOG_INTERVAL_MS,
|
||||
WATCHDOG_INTERVAL_MS,
|
||||
pendingIntent,
|
||||
|
||||
@@ -40,7 +40,16 @@ class OkHttpClientFactoryForRelays(
|
||||
const val DEFAULT_IS_MOBILE: Boolean = false
|
||||
const val DEFAULT_TIMEOUT_ON_WIFI_SECS: Int = 10
|
||||
const val DEFAULT_TIMEOUT_ON_MOBILE_SECS: Int = 30
|
||||
const val WEBSOCKET_PING_INTERVAL_SECS: Long = 120
|
||||
|
||||
// Every WebSocket ping on an otherwise-idle cellular connection promotes
|
||||
// the radio out of its low-power state and pays the multi-second "tail"
|
||||
// energy cost — per connection. On mobile data the interval is doubled to
|
||||
// halve those wake-ups while staying under the ~5-minute idle timeout of
|
||||
// the most aggressive carrier NATs (so connections aren't silently dropped
|
||||
// between pings). Wifi keeps the tighter interval: pings there are cheap
|
||||
// and detect dead connections sooner.
|
||||
const val WEBSOCKET_PING_INTERVAL_ON_WIFI_SECS: Long = 120
|
||||
const val WEBSOCKET_PING_INTERVAL_ON_MOBILE_SECS: Long = 240
|
||||
}
|
||||
|
||||
val myDispatcher =
|
||||
@@ -78,6 +87,7 @@ class OkHttpClientFactoryForRelays(
|
||||
fun buildHttpClient(
|
||||
proxy: Proxy?,
|
||||
timeoutSeconds: Int,
|
||||
pingIntervalSeconds: Long = WEBSOCKET_PING_INTERVAL_ON_WIFI_SECS,
|
||||
): OkHttpClient {
|
||||
if (proxy != lastProxy) {
|
||||
rootClient.connectionPool.evictAll()
|
||||
@@ -91,7 +101,7 @@ class OkHttpClientFactoryForRelays(
|
||||
.connectTimeout(Duration.ofSeconds(seconds.toLong()))
|
||||
.readTimeout(Duration.ofSeconds(seconds.toLong() * 3))
|
||||
.writeTimeout(Duration.ofSeconds(seconds.toLong() * 3))
|
||||
.pingInterval(Duration.ofSeconds(WEBSOCKET_PING_INTERVAL_SECS))
|
||||
.pingInterval(Duration.ofSeconds(pingIntervalSeconds))
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -102,12 +112,14 @@ class OkHttpClientFactoryForRelays(
|
||||
buildHttpClient(
|
||||
buildLocalSocksProxy(localSocksProxyPort),
|
||||
buildTimeout(isMobile ?: DEFAULT_IS_MOBILE),
|
||||
buildPingInterval(isMobile ?: DEFAULT_IS_MOBILE),
|
||||
)
|
||||
|
||||
fun buildHttpClient(isMobile: Boolean?): OkHttpClient =
|
||||
buildHttpClient(
|
||||
null,
|
||||
buildTimeout(isMobile ?: DEFAULT_IS_MOBILE),
|
||||
buildPingInterval(isMobile ?: DEFAULT_IS_MOBILE),
|
||||
)
|
||||
|
||||
fun buildTimeout(isMobile: Boolean): Int =
|
||||
@@ -117,5 +129,12 @@ class OkHttpClientFactoryForRelays(
|
||||
DEFAULT_TIMEOUT_ON_WIFI_SECS
|
||||
}
|
||||
|
||||
fun buildPingInterval(isMobile: Boolean): Long =
|
||||
if (isMobile) {
|
||||
WEBSOCKET_PING_INTERVAL_ON_MOBILE_SECS
|
||||
} else {
|
||||
WEBSOCKET_PING_INTERVAL_ON_WIFI_SECS
|
||||
}
|
||||
|
||||
fun buildLocalSocksProxy(port: Int?) = Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", port ?: DEFAULT_SOCKS_PORT))
|
||||
}
|
||||
|
||||
@@ -42,10 +42,15 @@ import java.util.concurrent.TimeUnit
|
||||
/**
|
||||
* Scans the scheduled-post store and publishes posts whose publish time has arrived.
|
||||
*
|
||||
* - schedule(context): periodic, every 15 min (WorkManager minimum).
|
||||
* - scheduleCatchUp(context): one-time, on app start, to flush posts that came
|
||||
* due while the device was off or while WorkManager
|
||||
* was deferred by Doze.
|
||||
* - schedule(context): periodic, every 15 min (WorkManager minimum). Only
|
||||
* enqueued while the store holds a PENDING post — the
|
||||
* store observer in AppModules schedules it when a
|
||||
* pending post appears and cancels it when the last
|
||||
* one drains, so the worker never wakes the process
|
||||
* with nothing to publish.
|
||||
* - scheduleCatchUp(context): one-time, to flush posts that came due while the
|
||||
* device was off or while WorkManager was deferred
|
||||
* by Doze.
|
||||
*/
|
||||
class ScheduledPostWorker(
|
||||
appContext: Context,
|
||||
@@ -107,6 +112,16 @@ class ScheduledPostWorker(
|
||||
Log.d(TAG) { "scheduleCatchUp(): enqueueUniqueWork($WORK_NAME_CATCH_UP, KEEP)" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the 15-minute periodic chain (keeps any catch-up run). Called by the
|
||||
* store observer in AppModules when the last PENDING post drains, so the
|
||||
* worker doesn't keep waking the process with nothing to publish.
|
||||
*/
|
||||
fun cancelPeriodic(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
|
||||
Log.d(TAG) { "cancelPeriodic(): cancelled periodic worker" }
|
||||
}
|
||||
|
||||
fun cancel(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME)
|
||||
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME_CATCH_UP)
|
||||
|
||||
@@ -105,11 +105,14 @@ fun CalendarReminderSettingsScreen(nav: INav) {
|
||||
onCheckedChange = {
|
||||
enabled = it
|
||||
prefs.setEnabled(it)
|
||||
// Toggling off doesn't cancel the worker — the worker itself short-
|
||||
// circuits when isEnabled() returns false. Keeping the schedule alive
|
||||
// means flipping it back on takes effect immediately without needing a
|
||||
// re-launch via AppModules.
|
||||
if (it) CalendarReminderWorker.schedule(context)
|
||||
// Cancel eagerly on disable so the periodic worker stops waking the
|
||||
// process; re-enabling re-schedules immediately, and the ACCEPTED-RSVP
|
||||
// observer in AppModules re-schedules on the next relevant RSVP too.
|
||||
if (it) {
|
||||
CalendarReminderWorker.schedule(context)
|
||||
} else {
|
||||
CalendarReminderWorker.cancel(context)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
@@ -107,7 +108,12 @@ class NostrClient(
|
||||
// new filters will be sent to the relays and a potential reconnect can
|
||||
// be triggered.
|
||||
// Default: STARTS active
|
||||
private var isActive = true
|
||||
//
|
||||
// A StateFlow (not a plain Boolean) so the keep-alive loop below can
|
||||
// suspend without any scheduled timer while the client is inactive —
|
||||
// e.g. the whole time the app sits in the background after the host
|
||||
// calls [disconnect].
|
||||
private val isActiveFlow = MutableStateFlow(true)
|
||||
|
||||
/**
|
||||
* Whatches for any changes in the relay list from subscriptions or outbox
|
||||
@@ -132,16 +138,16 @@ class NostrClient(
|
||||
|
||||
// Reconnects all relays that may have disconnected
|
||||
override fun connect() {
|
||||
isActive = true
|
||||
isActiveFlow.value = true
|
||||
relayPool.connect()
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
isActive = false
|
||||
isActiveFlow.value = false
|
||||
relayPool.disconnect()
|
||||
}
|
||||
|
||||
override fun isActive() = isActive
|
||||
override fun isActive() = isActiveFlow.value
|
||||
|
||||
class Reconnect(
|
||||
val onlyIfChanged: Boolean,
|
||||
@@ -173,12 +179,19 @@ class NostrClient(
|
||||
* error code) would stay disconnected forever in the absence of any
|
||||
* subscription change. The per-relay [BasicRelayClient] backoff still
|
||||
* gates the actual reconnect attempt, so dead relays are not hammered.
|
||||
*
|
||||
* While the client is inactive (the host called [disconnect], e.g. the
|
||||
* app moved to the background) the loop suspends on [isActiveFlow]
|
||||
* instead of ticking: no timer fires at all until [connect] flips the
|
||||
* flag back, saving periodic CPU wake-ups for the whole background
|
||||
* stretch.
|
||||
*/
|
||||
private val keepAliveJob =
|
||||
scope.launch {
|
||||
while (true) {
|
||||
isActiveFlow.first { it }
|
||||
delay(KEEP_ALIVE_INTERVAL_MS)
|
||||
if (this@NostrClient.isActive) {
|
||||
if (this@NostrClient.isActive()) {
|
||||
relayPool.reconnectIfNeedsTo(ignoreRetryDelays = false)
|
||||
}
|
||||
}
|
||||
@@ -202,7 +215,7 @@ class NostrClient(
|
||||
) {
|
||||
val relaysToUpdate = activeRequests.addOrUpdate(subId, filters, listener)
|
||||
|
||||
if (isActive) {
|
||||
if (isActive()) {
|
||||
activeRequests.sendToRelayIfChanged(subId, relaysToUpdate) { relay, cmd ->
|
||||
if (cmd is CloseCmd || cmd is AuthCmd) {
|
||||
relayPool.sendIfConnected(relay, cmd)
|
||||
@@ -225,7 +238,7 @@ class NostrClient(
|
||||
) {
|
||||
val relaysToUpdate = activeCounts.addOrUpdate(subId, filters)
|
||||
|
||||
if (isActive) {
|
||||
if (isActive()) {
|
||||
activeCounts.sendToRelayIfChanged(subId, relaysToUpdate) { relay, cmd ->
|
||||
if (cmd is CloseCmd || cmd is AuthCmd) {
|
||||
relayPool.sendIfConnected(relay, cmd)
|
||||
@@ -245,7 +258,7 @@ class NostrClient(
|
||||
) {
|
||||
val relaysToUpdate = eventOutbox.markAsSending(event, relayList)
|
||||
|
||||
if (isActive) {
|
||||
if (isActive()) {
|
||||
eventOutbox.sendToRelayIfChanged(event, relaysToUpdate, relayPool::sendOrConnectAndSync)
|
||||
|
||||
// wakes up all the other relays
|
||||
@@ -257,14 +270,14 @@ class NostrClient(
|
||||
val relaysToUpdateReqs = activeRequests.remove(subId)
|
||||
val relaysToUpdateCounts = activeCounts.remove(subId)
|
||||
|
||||
if (isActive) {
|
||||
if (isActive()) {
|
||||
activeRequests.sendToRelayIfChanged(subId, relaysToUpdateReqs, relayPool::sendIfConnected)
|
||||
activeCounts.sendToRelayIfChanged(subId, relaysToUpdateCounts, relayPool::sendIfConnected)
|
||||
}
|
||||
}
|
||||
|
||||
override fun syncFilters(relay: IRelayClient) {
|
||||
if (isActive) {
|
||||
if (isActive()) {
|
||||
scope.launch {
|
||||
activeRequests.syncState(relay.url, relay::sendOrConnectAndSync)
|
||||
activeCounts.syncState(relay.url, relay::sendOrConnectAndSync)
|
||||
@@ -337,7 +350,7 @@ class NostrClient(
|
||||
// The reconnect path is debounced and goes through
|
||||
// reconnectIfNeedsTo, which respects each relay's exponential
|
||||
// backoff so we don't hammer dead relays.
|
||||
if (isActive && relay.url in allRelays.value) {
|
||||
if (isActive() && relay.url in allRelays.value) {
|
||||
reconnect(onlyIfChanged = true)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user