mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
refactor(okhttp): inject AmethystDns instead of using a singleton
Drop the AmethystDns.shared lazy companion. Construct one amethystDns in AppModules and thread it through: - DualHttpClientManager / OkHttpClientFactory - DualHttpClientManagerForRelays / OkHttpClientFactoryForRelays - MediaCallEventListenerFactory / MediaCallEventListener - DnsInvalidatingEventListener.Factory - AmethystDnsStore This matches the dependency-injection style the rest of AppModules uses and lets tests inject a mock Dns where needed. Behavior is unchanged — every consumer still shares the same single instance, just by construction rather than by a static singleton.
This commit is contained in:
@@ -53,6 +53,7 @@ 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
|
||||
@@ -202,10 +203,15 @@ class AppModules(
|
||||
// Key cache service to download and decrypt encrypted files before caching them.
|
||||
val keyCache = EncryptionKeyCache()
|
||||
|
||||
// Persists the shared DNS resolver'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)
|
||||
// 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()
|
||||
|
||||
// Persists [amethystDns]'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)
|
||||
|
||||
// manages all the other connections separately from relays.
|
||||
val okHttpClients =
|
||||
@@ -215,6 +221,7 @@ class AppModules(
|
||||
isMobileDataProvider = connManager.isMobileOrNull,
|
||||
keyCache = keyCache,
|
||||
scope = applicationIOScope,
|
||||
dns = amethystDns,
|
||||
)
|
||||
|
||||
// Offers easy methods to know when connections are happening through Tor or not
|
||||
@@ -296,6 +303,7 @@ class AppModules(
|
||||
proxyPortProvider = torManager.activePortOrNull,
|
||||
isMobileDataProvider = connManager.isMobileOrNull,
|
||||
scope = applicationIOScope,
|
||||
dns = amethystDns,
|
||||
)
|
||||
|
||||
// Connects the INostrClient class with okHttp
|
||||
|
||||
@@ -273,12 +273,6 @@ class AmethystDns(
|
||||
Executors.newFixedThreadPool(8) { r ->
|
||||
Thread(r, "amethyst-dns-refresh").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Process-wide instance shared by every OkHttp client built in the app, so a host resolved
|
||||
* for an image fetch is reused when a relay handshake or NIP-05 lookup hits the same host.
|
||||
*/
|
||||
val shared: AmethystDns by lazy { AmethystDns() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ import com.vitorpamplona.quartz.utils.Log
|
||||
*/
|
||||
class AmethystDnsStore(
|
||||
private val context: Context,
|
||||
private val dns: AmethystDns = AmethystDns.shared,
|
||||
private val dns: AmethystDns,
|
||||
) {
|
||||
private val prefs by lazy { context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) }
|
||||
|
||||
|
||||
+10
-5
@@ -34,17 +34,22 @@ import java.io.IOException
|
||||
* Used by the relay client. The media path uses [MediaCallEventListener], which folds the same
|
||||
* invalidation into its `finish` method alongside its existing timing logging.
|
||||
*/
|
||||
class DnsInvalidatingEventListener private constructor() : EventListener() {
|
||||
class DnsInvalidatingEventListener(
|
||||
private val dns: AmethystDns,
|
||||
) : EventListener() {
|
||||
override fun callFailed(
|
||||
call: Call,
|
||||
ioe: IOException,
|
||||
) {
|
||||
AmethystDns.shared.invalidate(call.request().url.host)
|
||||
dns.invalidate(call.request().url.host)
|
||||
}
|
||||
|
||||
object Factory : EventListener.Factory {
|
||||
private val INSTANCE = DnsInvalidatingEventListener()
|
||||
/** Per-client factory. The listener is stateless, so the same instance serves every call. */
|
||||
class Factory(
|
||||
dns: AmethystDns,
|
||||
) : EventListener.Factory {
|
||||
private val listener = DnsInvalidatingEventListener(dns)
|
||||
|
||||
override fun create(call: Call): EventListener = INSTANCE
|
||||
override fun create(call: Call): EventListener = listener
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -38,8 +38,9 @@ class DualHttpClientManager(
|
||||
isMobileDataProvider: StateFlow<Boolean?>,
|
||||
keyCache: EncryptionKeyCache,
|
||||
scope: CoroutineScope,
|
||||
dns: AmethystDns,
|
||||
) : IHttpClientManager {
|
||||
val factory = OkHttpClientFactory(keyCache, userAgent)
|
||||
val factory = OkHttpClientFactory(keyCache, userAgent, dns)
|
||||
|
||||
val defaultHttpClient: StateFlow<OkHttpClient> =
|
||||
combine(proxyPortProvider, isMobileDataProvider) { proxy, mobile ->
|
||||
|
||||
+2
-1
@@ -35,8 +35,9 @@ class DualHttpClientManagerForRelays(
|
||||
proxyPortProvider: StateFlow<Int?>,
|
||||
isMobileDataProvider: StateFlow<Boolean?>,
|
||||
scope: CoroutineScope,
|
||||
dns: AmethystDns,
|
||||
) : IHttpClientManager {
|
||||
val factory = OkHttpClientFactoryForRelays(userAgent)
|
||||
val factory = OkHttpClientFactoryForRelays(userAgent, dns)
|
||||
|
||||
val defaultHttpClient: StateFlow<OkHttpClient> =
|
||||
combine(proxyPortProvider, isMobileDataProvider) { proxy, mobile ->
|
||||
|
||||
+4
-2
@@ -44,6 +44,7 @@ import java.net.Proxy
|
||||
class MediaCallEventListener(
|
||||
private val dispatcher: Dispatcher,
|
||||
private val connectionPool: ConnectionPool,
|
||||
private val dns: AmethystDns,
|
||||
) : EventListener() {
|
||||
private var callStartNanos = 0L
|
||||
private var dnsStartNanos = 0L
|
||||
@@ -129,7 +130,7 @@ class MediaCallEventListener(
|
||||
// dead IPs for up to 24h. Per-attempt connectFailed isn't enough — a multi-A-record
|
||||
// host can have one bad IP and OkHttp will recover by trying the next one.
|
||||
if (error != null) {
|
||||
AmethystDns.shared.invalidate(host)
|
||||
dns.invalidate(host)
|
||||
}
|
||||
|
||||
val totalMs = (System.nanoTime() - callStartNanos) / 1_000_000
|
||||
@@ -178,6 +179,7 @@ class MediaCallEventListener(
|
||||
class MediaCallEventListenerFactory(
|
||||
private val dispatcher: Dispatcher,
|
||||
private val connectionPool: ConnectionPool,
|
||||
private val dns: AmethystDns,
|
||||
) : EventListener.Factory {
|
||||
override fun create(call: Call): EventListener = MediaCallEventListener(dispatcher, connectionPool)
|
||||
override fun create(call: Call): EventListener = MediaCallEventListener(dispatcher, connectionPool, dns)
|
||||
}
|
||||
|
||||
+3
-2
@@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit
|
||||
class OkHttpClientFactory(
|
||||
keyCache: EncryptionKeyCache,
|
||||
val userAgent: String,
|
||||
private val dns: AmethystDns,
|
||||
) {
|
||||
// val logging = LoggingInterceptor()
|
||||
val keyDecryptor = EncryptedBlobInterceptor(keyCache)
|
||||
@@ -63,8 +64,8 @@ class OkHttpClientFactory(
|
||||
.Builder()
|
||||
.dispatcher(dispatcher)
|
||||
.connectionPool(connectionPool)
|
||||
.dns(AmethystDns.shared)
|
||||
.eventListenerFactory(MediaCallEventListenerFactory(dispatcher, connectionPool))
|
||||
.dns(dns)
|
||||
.eventListenerFactory(MediaCallEventListenerFactory(dispatcher, connectionPool, dns))
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.addInterceptor(DefaultContentTypeInterceptor(userAgent))
|
||||
|
||||
+3
-2
@@ -29,6 +29,7 @@ import java.time.Duration
|
||||
|
||||
class OkHttpClientFactoryForRelays(
|
||||
userAgent: String,
|
||||
private val dns: AmethystDns,
|
||||
) {
|
||||
companion object {
|
||||
// by picking a random proxy port, the connection will fail as it should.
|
||||
@@ -55,8 +56,8 @@ class OkHttpClientFactoryForRelays(
|
||||
OkHttpClient
|
||||
.Builder()
|
||||
.dispatcher(myDispatcher)
|
||||
.dns(AmethystDns.shared)
|
||||
.eventListenerFactory(DnsInvalidatingEventListener.Factory)
|
||||
.dns(dns)
|
||||
.eventListenerFactory(DnsInvalidatingEventListener.Factory(dns))
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.addInterceptor(DefaultContentTypeInterceptor(userAgent))
|
||||
|
||||
Reference in New Issue
Block a user