refactor(okhttp): rename AmethystDns -> SurgeDns
"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.
This commit is contained in:
@@ -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
|
||||
|
||||
+3
-3
@@ -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)
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class DualHttpClientManager(
|
||||
isMobileDataProvider: StateFlow<Boolean?>,
|
||||
keyCache: EncryptionKeyCache,
|
||||
scope: CoroutineScope,
|
||||
dns: AmethystDns,
|
||||
dns: SurgeDns,
|
||||
) : IHttpClientManager {
|
||||
val factory = OkHttpClientFactory(keyCache, userAgent, dns)
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class DualHttpClientManagerForRelays(
|
||||
proxyPortProvider: StateFlow<Int?>,
|
||||
isMobileDataProvider: StateFlow<Boolean?>,
|
||||
scope: CoroutineScope,
|
||||
dns: AmethystDns,
|
||||
dns: SurgeDns,
|
||||
) : IHttpClientManager {
|
||||
val factory = OkHttpClientFactoryForRelays(userAgent, dns)
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+2
-2
@@ -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<String>,
|
||||
+5
-5
@@ -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()
|
||||
+22
-22
@@ -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,
|
||||
Reference in New Issue
Block a user