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.AlwaysOnNotificationServiceManager
|
||||||
import com.vitorpamplona.amethyst.service.notifications.NotificationDispatcher
|
import com.vitorpamplona.amethyst.service.notifications.NotificationDispatcher
|
||||||
import com.vitorpamplona.amethyst.service.notifications.PokeyReceiver
|
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.DualHttpClientManager
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManagerForRelays
|
import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManagerForRelays
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache
|
import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
|
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.VideoCache
|
||||||
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory
|
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory
|
||||||
import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia
|
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
|
// 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
|
// resolved for an image fetch is reused when a relay handshake or NIP-05 lookup hits the
|
||||||
// same host.
|
// 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
|
// ~700 sync getaddrinfo calls. Restored entries fall through to the stale-while-revalidate
|
||||||
// path on first lookup.
|
// path on first lookup.
|
||||||
val dnsStore = AmethystDnsStore(appContext, amethystDns)
|
val dnsStore = SurgeDnsStore(appContext, surgeDns)
|
||||||
|
|
||||||
// manages all the other connections separately from relays.
|
// manages all the other connections separately from relays.
|
||||||
val okHttpClients =
|
val okHttpClients =
|
||||||
@@ -221,7 +221,7 @@ class AppModules(
|
|||||||
isMobileDataProvider = connManager.isMobileOrNull,
|
isMobileDataProvider = connManager.isMobileOrNull,
|
||||||
keyCache = keyCache,
|
keyCache = keyCache,
|
||||||
scope = applicationIOScope,
|
scope = applicationIOScope,
|
||||||
dns = amethystDns,
|
dns = surgeDns,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Offers easy methods to know when connections are happening through Tor or not
|
// Offers easy methods to know when connections are happening through Tor or not
|
||||||
@@ -303,7 +303,7 @@ class AppModules(
|
|||||||
proxyPortProvider = torManager.activePortOrNull,
|
proxyPortProvider = torManager.activePortOrNull,
|
||||||
isMobileDataProvider = connManager.isMobileOrNull,
|
isMobileDataProvider = connManager.isMobileOrNull,
|
||||||
scope = applicationIOScope,
|
scope = applicationIOScope,
|
||||||
dns = amethystDns,
|
dns = surgeDns,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connects the INostrClient class with okHttp
|
// Connects the INostrClient class with okHttp
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ import okhttp3.EventListener
|
|||||||
import java.io.IOException
|
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)
|
* `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
|
* 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
|
* 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.
|
* invalidation into its `finish` method alongside its existing timing logging.
|
||||||
*/
|
*/
|
||||||
class DnsInvalidatingEventListener(
|
class DnsInvalidatingEventListener(
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) : EventListener() {
|
) : EventListener() {
|
||||||
override fun callFailed(
|
override fun callFailed(
|
||||||
call: Call,
|
call: Call,
|
||||||
@@ -46,7 +46,7 @@ class DnsInvalidatingEventListener(
|
|||||||
|
|
||||||
/** Per-client factory. The listener is stateless, so the same instance serves every call. */
|
/** Per-client factory. The listener is stateless, so the same instance serves every call. */
|
||||||
class Factory(
|
class Factory(
|
||||||
dns: AmethystDns,
|
dns: SurgeDns,
|
||||||
) : EventListener.Factory {
|
) : EventListener.Factory {
|
||||||
private val listener = DnsInvalidatingEventListener(dns)
|
private val listener = DnsInvalidatingEventListener(dns)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ class DualHttpClientManager(
|
|||||||
isMobileDataProvider: StateFlow<Boolean?>,
|
isMobileDataProvider: StateFlow<Boolean?>,
|
||||||
keyCache: EncryptionKeyCache,
|
keyCache: EncryptionKeyCache,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
dns: AmethystDns,
|
dns: SurgeDns,
|
||||||
) : IHttpClientManager {
|
) : IHttpClientManager {
|
||||||
val factory = OkHttpClientFactory(keyCache, userAgent, dns)
|
val factory = OkHttpClientFactory(keyCache, userAgent, dns)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ class DualHttpClientManagerForRelays(
|
|||||||
proxyPortProvider: StateFlow<Int?>,
|
proxyPortProvider: StateFlow<Int?>,
|
||||||
isMobileDataProvider: StateFlow<Boolean?>,
|
isMobileDataProvider: StateFlow<Boolean?>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
dns: AmethystDns,
|
dns: SurgeDns,
|
||||||
) : IHttpClientManager {
|
) : IHttpClientManager {
|
||||||
val factory = OkHttpClientFactoryForRelays(userAgent, dns)
|
val factory = OkHttpClientFactoryForRelays(userAgent, dns)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ import java.net.Proxy
|
|||||||
class MediaCallEventListener(
|
class MediaCallEventListener(
|
||||||
private val dispatcher: Dispatcher,
|
private val dispatcher: Dispatcher,
|
||||||
private val connectionPool: ConnectionPool,
|
private val connectionPool: ConnectionPool,
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) : EventListener() {
|
) : EventListener() {
|
||||||
private var callStartNanos = 0L
|
private var callStartNanos = 0L
|
||||||
private var dnsStartNanos = 0L
|
private var dnsStartNanos = 0L
|
||||||
@@ -179,7 +179,7 @@ class MediaCallEventListener(
|
|||||||
class MediaCallEventListenerFactory(
|
class MediaCallEventListenerFactory(
|
||||||
private val dispatcher: Dispatcher,
|
private val dispatcher: Dispatcher,
|
||||||
private val connectionPool: ConnectionPool,
|
private val connectionPool: ConnectionPool,
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) : EventListener.Factory {
|
) : EventListener.Factory {
|
||||||
override fun create(call: Call): EventListener = MediaCallEventListener(dispatcher, connectionPool, dns)
|
override fun create(call: Call): EventListener = MediaCallEventListener(dispatcher, connectionPool, dns)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
class OkHttpClientFactory(
|
class OkHttpClientFactory(
|
||||||
keyCache: EncryptionKeyCache,
|
keyCache: EncryptionKeyCache,
|
||||||
val userAgent: String,
|
val userAgent: String,
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) {
|
) {
|
||||||
// val logging = LoggingInterceptor()
|
// val logging = LoggingInterceptor()
|
||||||
val keyDecryptor = EncryptedBlobInterceptor(keyCache)
|
val keyDecryptor = EncryptedBlobInterceptor(keyCache)
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ import java.time.Duration
|
|||||||
|
|
||||||
class OkHttpClientFactoryForRelays(
|
class OkHttpClientFactoryForRelays(
|
||||||
userAgent: String,
|
userAgent: String,
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// by picking a random proxy port, the connection will fail as it should.
|
// 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
|
* (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.
|
* lookup block on `future.get()`. Background refreshes never block any caller.
|
||||||
*/
|
*/
|
||||||
class AmethystDns(
|
class SurgeDns(
|
||||||
private val delegate: Dns = Dns.SYSTEM,
|
private val delegate: Dns = Dns.SYSTEM,
|
||||||
private val maxEntries: Int = 2000,
|
private val maxEntries: Int = 2000,
|
||||||
private val positiveTtlMs: Long = TimeUnit.HOURS.toMillis(24),
|
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(
|
data class DnsCacheRecord(
|
||||||
val hostname: String,
|
val hostname: String,
|
||||||
val addresses: List<String>,
|
val addresses: List<String>,
|
||||||
+5
-5
@@ -26,7 +26,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
|
|||||||
import com.vitorpamplona.quartz.utils.Log
|
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.
|
* starts hot after a process restart.
|
||||||
*
|
*
|
||||||
* Cold starts are the resolver's worst case — every host is a sync `getaddrinfo`. With a
|
* 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 ≈
|
* list, Coil's image cache, and the system resolver's own state. ~700 entries × ~80 bytes ≈
|
||||||
* ~55 KB of JSON.
|
* ~55 KB of JSON.
|
||||||
*/
|
*/
|
||||||
class AmethystDnsStore(
|
class SurgeDnsStore(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val dns: AmethystDns,
|
private val dns: SurgeDns,
|
||||||
) {
|
) {
|
||||||
private val prefs by lazy { context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
|
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
|
* 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.
|
* call from a background thread.
|
||||||
*/
|
*/
|
||||||
fun load() {
|
fun load() {
|
||||||
@@ -93,7 +93,7 @@ class AmethystDnsStore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "AmethystDnsStore"
|
private const val TAG = "SurgeDnsStore"
|
||||||
private const val PREFS_NAME = "amethyst_dns_cache"
|
private const val PREFS_NAME = "amethyst_dns_cache"
|
||||||
private const val KEY_SNAPSHOT = "dns_cache_v1"
|
private const val KEY_SNAPSHOT = "dns_cache_v1"
|
||||||
private val MAPPER = jacksonObjectMapper()
|
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.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
class AmethystDnsTest {
|
class SurgeDnsTest {
|
||||||
private fun ip(value: String) = InetAddress.getByName(value)
|
private fun ip(value: String) = InetAddress.getByName(value)
|
||||||
|
|
||||||
private class CountingDns(
|
private class CountingDns(
|
||||||
@@ -71,7 +71,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `cache hit avoids second upstream call`() {
|
fun `cache hit avoids second upstream call`() {
|
||||||
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
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 first = dns.lookup("a.example")
|
||||||
val second = dns.lookup("a.example")
|
val second = dns.lookup("a.example")
|
||||||
@@ -84,7 +84,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `negative cache short-circuits subsequent lookups`() {
|
fun `negative cache short-circuits subsequent lookups`() {
|
||||||
val upstream = CountingDns(emptyMap())
|
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") }
|
||||||
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 upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
||||||
val syncRefresh = Executor { it.run() }
|
val syncRefresh = Executor { it.run() }
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 1,
|
positiveTtlMs = 1,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -117,7 +117,7 @@ class AmethystDnsTest {
|
|||||||
fun `expired negative entry does not stale-while-revalidate`() {
|
fun `expired negative entry does not stale-while-revalidate`() {
|
||||||
val upstream = CountingDns(emptyMap())
|
val upstream = CountingDns(emptyMap())
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
negativeTtlMs = 1,
|
negativeTtlMs = 1,
|
||||||
@@ -142,7 +142,7 @@ class AmethystDnsTest {
|
|||||||
}
|
}
|
||||||
val syncRefresh = Executor { it.run() }
|
val syncRefresh = Executor { it.run() }
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 1,
|
positiveTtlMs = 1,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -185,7 +185,7 @@ class AmethystDnsTest {
|
|||||||
}
|
}
|
||||||
val pool = Executors.newFixedThreadPool(8)
|
val pool = Executors.newFixedThreadPool(8)
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = dynamic,
|
delegate = dynamic,
|
||||||
positiveTtlMs = 1,
|
positiveTtlMs = 1,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -225,7 +225,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `concurrent lookups for the same host coalesce to one upstream call`() {
|
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 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)
|
val pool = Executors.newFixedThreadPool(8)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -263,7 +263,7 @@ class AmethystDnsTest {
|
|||||||
parallelism.decrementAndGet()
|
parallelism.decrementAndGet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val dns = AmethystDns(delegate = instrumented)
|
val dns = SurgeDns(delegate = instrumented)
|
||||||
val pool = Executors.newFixedThreadPool(2)
|
val pool = Executors.newFixedThreadPool(2)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -289,7 +289,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `invalidate clears cache so next lookup hits upstream`() {
|
fun `invalidate clears cache so next lookup hits upstream`() {
|
||||||
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
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.lookup("a.example")
|
||||||
dns.invalidate()
|
dns.invalidate()
|
||||||
@@ -307,7 +307,7 @@ class AmethystDnsTest {
|
|||||||
"b.example" to listOf(ip("5.6.7.8")),
|
"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("a.example")
|
||||||
dns.lookup("b.example")
|
dns.lookup("b.example")
|
||||||
@@ -329,7 +329,7 @@ class AmethystDnsTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 60_000,
|
positiveTtlMs = 60_000,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -346,7 +346,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `restore replays cached entries without hitting upstream`() {
|
fun `restore replays cached entries without hitting upstream`() {
|
||||||
val upstream = CountingDns(mapOf("relay.example" to listOf(ip("9.9.9.9"))))
|
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
|
val expiresAt = System.currentTimeMillis() + 60_000
|
||||||
dns.restore(listOf(DnsCacheRecord("relay.example", listOf("9.9.9.9"), expiresAt)))
|
dns.restore(listOf(DnsCacheRecord("relay.example", listOf("9.9.9.9"), expiresAt)))
|
||||||
@@ -358,7 +358,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `restore drops entries already expired on disk`() {
|
fun `restore drops entries already expired on disk`() {
|
||||||
val upstream = CountingDns(mapOf("relay.example" to listOf(ip("9.9.9.9"))))
|
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
|
val expiredAt = System.currentTimeMillis() - 1_000
|
||||||
dns.restore(listOf(DnsCacheRecord("relay.example", listOf("9.9.9.9"), expiredAt)))
|
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`() {
|
fun `restore does not overwrite a fresh in-memory entry`() {
|
||||||
val upstream = CountingDns(mapOf("relay.example" to listOf(ip("1.1.1.1"))))
|
val upstream = CountingDns(mapOf("relay.example" to listOf(ip("1.1.1.1"))))
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 60_000,
|
positiveTtlMs = 60_000,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -396,7 +396,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `lookup is case-insensitive`() {
|
fun `lookup is case-insensitive`() {
|
||||||
val upstream = CountingDns(mapOf("example.com" to listOf(ip("1.2.3.4"))))
|
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")
|
||||||
dns.lookup("example.com")
|
dns.lookup("example.com")
|
||||||
@@ -408,7 +408,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `invalidate is case-insensitive`() {
|
fun `invalidate is case-insensitive`() {
|
||||||
val upstream = CountingDns(mapOf("example.com" to listOf(ip("1.2.3.4"))))
|
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")
|
||||||
dns.invalidate("EXAMPLE.com")
|
dns.invalidate("EXAMPLE.com")
|
||||||
@@ -420,7 +420,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `restore lowercases hostnames so subsequent lookups hit`() {
|
fun `restore lowercases hostnames so subsequent lookups hit`() {
|
||||||
val upstream = CountingDns(mapOf("example.com" to listOf(ip("9.9.9.9"))))
|
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(
|
dns.restore(
|
||||||
listOf(
|
listOf(
|
||||||
@@ -436,7 +436,7 @@ class AmethystDnsTest {
|
|||||||
fun `dirty flag tracks positive writes`() {
|
fun `dirty flag tracks positive writes`() {
|
||||||
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 60_000,
|
positiveTtlMs = 60_000,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -455,7 +455,7 @@ class AmethystDnsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `failed lookup does not mark cache dirty`() {
|
fun `failed lookup does not mark cache dirty`() {
|
||||||
val upstream = CountingDns(emptyMap())
|
val upstream = CountingDns(emptyMap())
|
||||||
val dns = AmethystDns(delegate = upstream)
|
val dns = SurgeDns(delegate = upstream)
|
||||||
|
|
||||||
assertFalse(dns.isDirty())
|
assertFalse(dns.isDirty())
|
||||||
runCatching { dns.lookup("missing.example") }
|
runCatching { dns.lookup("missing.example") }
|
||||||
@@ -471,7 +471,7 @@ class AmethystDnsTest {
|
|||||||
}
|
}
|
||||||
val syncRefresh = Executor { it.run() }
|
val syncRefresh = Executor { it.run() }
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 1,
|
positiveTtlMs = 1,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
@@ -499,7 +499,7 @@ class AmethystDnsTest {
|
|||||||
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
val upstream = CountingDns(mapOf("a.example" to listOf(ip("1.2.3.4"))))
|
||||||
val rejecting = Executor { throw RejectedExecutionException("test") }
|
val rejecting = Executor { throw RejectedExecutionException("test") }
|
||||||
val dns =
|
val dns =
|
||||||
AmethystDns(
|
SurgeDns(
|
||||||
delegate = upstream,
|
delegate = upstream,
|
||||||
positiveTtlMs = 1,
|
positiveTtlMs = 1,
|
||||||
positiveTtlJitterMs = 0,
|
positiveTtlJitterMs = 0,
|
||||||
Reference in New Issue
Block a user