diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/RealDnsCache.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/RealDnsCache.kt index eb95b73a5cf3..bbf46d1abe66 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/RealDnsCache.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/dns/RealDnsCache.kt @@ -205,9 +205,18 @@ class RealDnsCache( val previous = entry.state.get() val sentAt = previous.inFlightCall!!.sentAt val timeToLive = - (dnsResponse.answers.minOfOrNull { it.timeToLive } ?: 0) - .seconds - .coerceIn(minimumTimeToLive, maximumTimeToLive) + when { + dnsResponse.answers.isEmpty() -> { + failureTimeToLive + } + + else -> { + dnsResponse.answers + .minOf { it.timeToLive } + .seconds + .coerceIn(minimumTimeToLive, maximumTimeToLive) + } + } val revalidateDelay = (timeToLive - revalidateBeforeExpire).coerceAtLeast(0.seconds) val next = diff --git a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt index 13d2f94bd31c..0871a2aef844 100644 --- a/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt +++ b/okhttp/src/commonTest/kotlin/okhttp3/internal/dns/StateMachineDnsCallTest.kt @@ -738,6 +738,67 @@ class StateMachineDnsCallTest { assertThat(cache.hitCount).isEqualTo(0) } + @Test + fun `empty result expires on the same schedule as failure`() = + testStateMachineDnsCall { + val call0 = + newCall( + request = Dns.Request(hostname = "lysine.dev"), + caching = true, + includeIPv6 = false, + includeServiceMetadata = false, + ) + call0.enqueue() + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_A, + addresses = listOf(), + ) + call0.takeOnRecordsIpAddresses( + last = true, + addresses = listOf(), + ) + + // The empty result is still cached. + val call1 = + newCall( + request = Dns.Request(hostname = "lysine.dev"), + caching = true, + includeIPv6 = false, + includeServiceMetadata = false, + ) + call1.enqueue() + call1.takeOnRecordsIpAddresses( + last = true, + addresses = listOf(), + ) + + // The empty result expires after 5 seconds. + sleep(5.seconds) + val call2 = + newCall( + request = Dns.Request(hostname = "lysine.dev"), + caching = true, + includeIPv6 = false, + includeServiceMetadata = false, + ) + call2.enqueue() + queryFactory.respondToQuery( + hostname = "lysine.dev", + type = TYPE_A, + addresses = blueIpv4s, + ) + call2.takeOnRecordsIpAddresses( + last = true, + addresses = blueIpv4s, + ) + + assertThat(cache.size).isEqualTo(1) + assertThat(cache.requestCount).isEqualTo(3) + assertThat(cache.networkCount).isEqualTo(2) + assertThat(cache.hitCount).isEqualTo(1) + } + @Test fun `failure is revalidated`() = testStateMachineDnsCall {