From b9a260f3ceaba8013f6857e448c016b177bb6b16 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 3 May 2026 21:26:46 +0000 Subject: [PATCH] refactor(okhttp): rename AmethystDns -> SurgeDns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "AmethystDns" identified the owner; "SurgeDns" identifies the behavior. The class is built to absorb sudden bursts of concurrent DNS work — 700 relays reconnecting, a feed scrolling through dozens of media hosts, a profile screen requesting a NIP-05 — without falling over: lock-free reads, single-flight coalescing, and stale-while-revalidate so recurring hosts never block. File renames: - AmethystDns.kt -> SurgeDns.kt - AmethystDnsStore.kt -> SurgeDnsStore.kt - AmethystDnsTest.kt -> SurgeDnsTest.kt Symbol renames in every touchpoint (AppModules, factories, managers, event listeners): AmethystDns -> SurgeDns, amethystDns -> surgeDns. --- .../com/vitorpamplona/amethyst/AppModules.kt | 14 +++--- .../okhttp/DnsInvalidatingEventListener.kt | 6 +-- .../service/okhttp/DualHttpClientManager.kt | 2 +- .../okhttp/DualHttpClientManagerForRelays.kt | 2 +- .../service/okhttp/MediaCallEventListener.kt | 4 +- .../service/okhttp/OkHttpClientFactory.kt | 2 +- .../okhttp/OkHttpClientFactoryForRelays.kt | 2 +- .../okhttp/{AmethystDns.kt => SurgeDns.kt} | 4 +- .../{AmethystDnsStore.kt => SurgeDnsStore.kt} | 10 ++--- .../{AmethystDnsTest.kt => SurgeDnsTest.kt} | 44 +++++++++---------- 10 files changed, 45 insertions(+), 45 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/{AmethystDns.kt => SurgeDns.kt} (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/{AmethystDnsStore.kt => SurgeDnsStore.kt} (93%) rename amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/{AmethystDnsTest.kt => SurgeDnsTest.kt} (95%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt index 7555e6c5b..e0e0dc6d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt @@ -53,12 +53,12 @@ import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.notifications.AlwaysOnNotificationServiceManager import com.vitorpamplona.amethyst.service.notifications.NotificationDispatcher import com.vitorpamplona.amethyst.service.notifications.PokeyReceiver -import com.vitorpamplona.amethyst.service.okhttp.AmethystDns -import com.vitorpamplona.amethyst.service.okhttp.AmethystDnsStore import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManagerForRelays import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket +import com.vitorpamplona.amethyst.service.okhttp.SurgeDns +import com.vitorpamplona.amethyst.service.okhttp.SurgeDnsStore import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia @@ -206,12 +206,12 @@ class AppModules( // Concurrent, caching DNS resolver shared by every OkHttp client built below — a host // resolved for an image fetch is reused when a relay handshake or NIP-05 lookup hits the // same host. - val amethystDns = AmethystDns() + val surgeDns = SurgeDns() - // Persists [amethystDns]'s positive cache across process restarts so cold starts don't pay + // Persists [surgeDns]'s positive cache across process restarts so cold starts don't pay // ~700 sync getaddrinfo calls. Restored entries fall through to the stale-while-revalidate // path on first lookup. - val dnsStore = AmethystDnsStore(appContext, amethystDns) + val dnsStore = SurgeDnsStore(appContext, surgeDns) // manages all the other connections separately from relays. val okHttpClients = @@ -221,7 +221,7 @@ class AppModules( isMobileDataProvider = connManager.isMobileOrNull, keyCache = keyCache, scope = applicationIOScope, - dns = amethystDns, + dns = surgeDns, ) // Offers easy methods to know when connections are happening through Tor or not @@ -303,7 +303,7 @@ class AppModules( proxyPortProvider = torManager.activePortOrNull, isMobileDataProvider = connManager.isMobileOrNull, scope = applicationIOScope, - dns = amethystDns, + dns = surgeDns, ) // Connects the INostrClient class with okHttp diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DnsInvalidatingEventListener.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DnsInvalidatingEventListener.kt index 3da60a7ac..569652994 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DnsInvalidatingEventListener.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DnsInvalidatingEventListener.kt @@ -25,7 +25,7 @@ import okhttp3.EventListener import java.io.IOException /** - * Drops a host's [AmethystDns] entry whenever an OkHttp call to it fails outright. We hook + * Drops a host's [SurgeDns] entry whenever an OkHttp call to it fails outright. We hook * `callFailed` (final-stage signal after OkHttp has tried every address from the DNS lookup) * rather than per-attempt `connectFailed`: a multi-A-record host with one bad IP fires the * latter while OkHttp recovers via the next address, and we don't want to invalidate the cache @@ -35,7 +35,7 @@ import java.io.IOException * invalidation into its `finish` method alongside its existing timing logging. */ class DnsInvalidatingEventListener( - private val dns: AmethystDns, + private val dns: SurgeDns, ) : EventListener() { override fun callFailed( call: Call, @@ -46,7 +46,7 @@ class DnsInvalidatingEventListener( /** Per-client factory. The listener is stateless, so the same instance serves every call. */ class Factory( - dns: AmethystDns, + dns: SurgeDns, ) : EventListener.Factory { private val listener = DnsInvalidatingEventListener(dns) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManager.kt index 460f90250..32679800f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManager.kt @@ -38,7 +38,7 @@ class DualHttpClientManager( isMobileDataProvider: StateFlow, keyCache: EncryptionKeyCache, scope: CoroutineScope, - dns: AmethystDns, + dns: SurgeDns, ) : IHttpClientManager { val factory = OkHttpClientFactory(keyCache, userAgent, dns) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManagerForRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManagerForRelays.kt index 891ccfae9..d190c885d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManagerForRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/DualHttpClientManagerForRelays.kt @@ -35,7 +35,7 @@ class DualHttpClientManagerForRelays( proxyPortProvider: StateFlow, isMobileDataProvider: StateFlow, scope: CoroutineScope, - dns: AmethystDns, + dns: SurgeDns, ) : IHttpClientManager { val factory = OkHttpClientFactoryForRelays(userAgent, dns) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/MediaCallEventListener.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/MediaCallEventListener.kt index b7b399d42..de863490d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/MediaCallEventListener.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/MediaCallEventListener.kt @@ -44,7 +44,7 @@ import java.net.Proxy class MediaCallEventListener( private val dispatcher: Dispatcher, private val connectionPool: ConnectionPool, - private val dns: AmethystDns, + private val dns: SurgeDns, ) : EventListener() { private var callStartNanos = 0L private var dnsStartNanos = 0L @@ -179,7 +179,7 @@ class MediaCallEventListener( class MediaCallEventListenerFactory( private val dispatcher: Dispatcher, private val connectionPool: ConnectionPool, - private val dns: AmethystDns, + private val dns: SurgeDns, ) : EventListener.Factory { override fun create(call: Call): EventListener = MediaCallEventListener(dispatcher, connectionPool, dns) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt index de24b6a86..d208b511e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt @@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit class OkHttpClientFactory( keyCache: EncryptionKeyCache, val userAgent: String, - private val dns: AmethystDns, + private val dns: SurgeDns, ) { // val logging = LoggingInterceptor() val keyDecryptor = EncryptedBlobInterceptor(keyCache) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt index 10983a6d4..a5e3619a6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt @@ -29,7 +29,7 @@ import java.time.Duration class OkHttpClientFactoryForRelays( userAgent: String, - private val dns: AmethystDns, + private val dns: SurgeDns, ) { companion object { // by picking a random proxy port, the connection will fail as it should. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDns.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDns.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDns.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDns.kt index 98101a8f7..50bf1449f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDns.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDns.kt @@ -69,7 +69,7 @@ import java.util.concurrent.atomic.AtomicBoolean * (unavoidable — there's nothing to serve stale yet), and followers waiting on that first * lookup block on `future.get()`. Background refreshes never block any caller. */ -class AmethystDns( +class SurgeDns( private val delegate: Dns = Dns.SYSTEM, private val maxEntries: Int = 2000, private val positiveTtlMs: Long = TimeUnit.HOURS.toMillis(24), @@ -286,7 +286,7 @@ class AmethystDns( } } -/** Persistable record. Public so [AmethystDnsStore] can serialize it via Jackson. */ +/** Persistable record. Public so [SurgeDnsStore] can serialize it via Jackson. */ data class DnsCacheRecord( val hostname: String, val addresses: List, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsStore.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsStore.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsStore.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsStore.kt index d80f34dd2..c460f1210 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsStore.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsStore.kt @@ -26,7 +26,7 @@ import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.quartz.utils.Log /** - * Persists [AmethystDns]'s positive cache to a small `SharedPreferences` blob so the resolver + * Persists [SurgeDns]'s positive cache to a small `SharedPreferences` blob so the resolver * starts hot after a process restart. * * Cold starts are the resolver's worst case — every host is a sync `getaddrinfo`. With a @@ -38,15 +38,15 @@ import com.vitorpamplona.quartz.utils.Log * list, Coil's image cache, and the system resolver's own state. ~700 entries × ~80 bytes ≈ * ~55 KB of JSON. */ -class AmethystDnsStore( +class SurgeDnsStore( private val context: Context, - private val dns: AmethystDns, + private val dns: SurgeDns, ) { private val prefs by lazy { context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) } /** * Read the persisted snapshot and merge it into the resolver. Existing in-memory entries - * are preserved (see [AmethystDns.restore]). Safe to call once at app start. Blocking I/O — + * are preserved (see [SurgeDns.restore]). Safe to call once at app start. Blocking I/O — * call from a background thread. */ fun load() { @@ -93,7 +93,7 @@ class AmethystDnsStore( } companion object { - private const val TAG = "AmethystDnsStore" + private const val TAG = "SurgeDnsStore" private const val PREFS_NAME = "amethyst_dns_cache" private const val KEY_SNAPSHOT = "dns_cache_v1" private val MAPPER = jacksonObjectMapper() diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsTest.kt similarity index 95% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsTest.kt rename to amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsTest.kt index 189a9ecb9..ff7ad1fd6 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/AmethystDnsTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/SurgeDnsTest.kt @@ -37,7 +37,7 @@ import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference -class AmethystDnsTest { +class SurgeDnsTest { private fun ip(value: String) = InetAddress.getByName(value) private class CountingDns( @@ -71,7 +71,7 @@ class AmethystDnsTest { @Test fun `cache hit avoids second upstream call`() { val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) val first = dns.lookup("a.example") val second = dns.lookup("a.example") @@ -84,7 +84,7 @@ class AmethystDnsTest { @Test fun `negative cache short-circuits subsequent lookups`() { val upstream = CountingDns(emptyMap()) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) assertThrows(UnknownHostException::class.java) { dns.lookup("missing.example") } assertThrows(UnknownHostException::class.java) { dns.lookup("missing.example") } @@ -97,7 +97,7 @@ class AmethystDnsTest { val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4")))) val syncRefresh = Executor { it.run() } val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 1, positiveTtlJitterMs = 0, @@ -117,7 +117,7 @@ class AmethystDnsTest { fun `expired negative entry does not stale-while-revalidate`() { val upstream = CountingDns(emptyMap()) val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlJitterMs = 0, negativeTtlMs = 1, @@ -142,7 +142,7 @@ class AmethystDnsTest { } val syncRefresh = Executor { it.run() } val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 1, positiveTtlJitterMs = 0, @@ -185,7 +185,7 @@ class AmethystDnsTest { } val pool = Executors.newFixedThreadPool(8) val dns = - AmethystDns( + SurgeDns( delegate = dynamic, positiveTtlMs = 1, positiveTtlJitterMs = 0, @@ -225,7 +225,7 @@ class AmethystDnsTest { @Test fun `concurrent lookups for the same host coalesce to one upstream call`() { val gated = GatedDns(mapOf("hot.example" to listOf(ip("9.9.9.9")))) - val dns = AmethystDns(delegate = gated) + val dns = SurgeDns(delegate = gated) val pool = Executors.newFixedThreadPool(8) try { @@ -263,7 +263,7 @@ class AmethystDnsTest { parallelism.decrementAndGet() } } - val dns = AmethystDns(delegate = instrumented) + val dns = SurgeDns(delegate = instrumented) val pool = Executors.newFixedThreadPool(2) try { @@ -289,7 +289,7 @@ class AmethystDnsTest { @Test fun `invalidate clears cache so next lookup hits upstream`() { val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) dns.lookup("a.example") dns.invalidate() @@ -307,7 +307,7 @@ class AmethystDnsTest { "b.example" to listOf(ip("5.6.7.8")), ), ) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) dns.lookup("a.example") dns.lookup("b.example") @@ -329,7 +329,7 @@ class AmethystDnsTest { ), ) val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 60_000, positiveTtlJitterMs = 0, @@ -346,7 +346,7 @@ class AmethystDnsTest { @Test fun `restore replays cached entries without hitting upstream`() { val upstream = CountingDns(mapOf("relay.example" to listOf(ip("9.9.9.9")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) val expiresAt = System.currentTimeMillis() + 60_000 dns.restore(listOf(DnsCacheRecord("relay.example", listOf("9.9.9.9"), expiresAt))) @@ -358,7 +358,7 @@ class AmethystDnsTest { @Test fun `restore drops entries already expired on disk`() { val upstream = CountingDns(mapOf("relay.example" to listOf(ip("9.9.9.9")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) val expiredAt = System.currentTimeMillis() - 1_000 dns.restore(listOf(DnsCacheRecord("relay.example", listOf("9.9.9.9"), expiredAt))) @@ -371,7 +371,7 @@ class AmethystDnsTest { fun `restore does not overwrite a fresh in-memory entry`() { val upstream = CountingDns(mapOf("relay.example" to listOf(ip("1.1.1.1")))) val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 60_000, positiveTtlJitterMs = 0, @@ -396,7 +396,7 @@ class AmethystDnsTest { @Test fun `lookup is case-insensitive`() { val upstream = CountingDns(mapOf("example.com" to listOf(ip("1.2.3.4")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) dns.lookup("Example.COM") dns.lookup("example.com") @@ -408,7 +408,7 @@ class AmethystDnsTest { @Test fun `invalidate is case-insensitive`() { val upstream = CountingDns(mapOf("example.com" to listOf(ip("1.2.3.4")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) dns.lookup("example.com") dns.invalidate("EXAMPLE.com") @@ -420,7 +420,7 @@ class AmethystDnsTest { @Test fun `restore lowercases hostnames so subsequent lookups hit`() { val upstream = CountingDns(mapOf("example.com" to listOf(ip("9.9.9.9")))) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) dns.restore( listOf( @@ -436,7 +436,7 @@ class AmethystDnsTest { fun `dirty flag tracks positive writes`() { val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4")))) val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 60_000, positiveTtlJitterMs = 0, @@ -455,7 +455,7 @@ class AmethystDnsTest { @Test fun `failed lookup does not mark cache dirty`() { val upstream = CountingDns(emptyMap()) - val dns = AmethystDns(delegate = upstream) + val dns = SurgeDns(delegate = upstream) assertFalse(dns.isDirty()) runCatching { dns.lookup("missing.example") } @@ -471,7 +471,7 @@ class AmethystDnsTest { } val syncRefresh = Executor { it.run() } val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 1, positiveTtlJitterMs = 0, @@ -499,7 +499,7 @@ class AmethystDnsTest { val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4")))) val rejecting = Executor { throw RejectedExecutionException("test") } val dns = - AmethystDns( + SurgeDns( delegate = upstream, positiveTtlMs = 1, positiveTtlJitterMs = 0,