-
Notifications
You must be signed in to change notification settings - Fork 9.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Discussion] Add more connection failure tracking and tests. #7588
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1022,6 +1022,8 @@ open class CallTest { | |
executeSynchronously(request) | ||
.assertCode(200) | ||
.assertBody("success!") | ||
|
||
assertThat(client.routeDatabase.failedRoutes.single().proxy.address()).isEqualTo(server.toProxyAddress().address()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only failing test when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or perhaps, the coverage we do have (in RouteFailureTest) is overly pessimistic?? |
||
} | ||
|
||
/** https://github.com/square/okhttp/issues/1801 */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,6 @@ package okhttp3 | |
|
||
import java.io.IOException | ||
import java.net.InetAddress | ||
import java.net.InetSocketAddress | ||
import mockwebserver3.MockResponse | ||
import mockwebserver3.MockWebServer | ||
import mockwebserver3.SocketPolicy | ||
|
@@ -27,11 +26,11 @@ import okhttp3.testing.PlatformRule | |
import okhttp3.tls.internal.TlsUtil.localhost | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.extension.RegisterExtension | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.ValueSource | ||
|
||
class RouteFailureTest { | ||
private lateinit var socketFactory: SpecificHostSocketFactory | ||
private lateinit var client: OkHttpClient | ||
|
||
@RegisterExtension | ||
|
@@ -45,12 +44,16 @@ class RouteFailureTest { | |
|
||
private var listener = RecordingEventListener() | ||
|
||
private var socketFactory: SpecificHostSocketFactory = SpecificHostSocketFactory(defaultAddress = null) | ||
|
||
private val handshakeCertificates = localhost() | ||
|
||
val dns = FakeDns() | ||
|
||
val ipv4 = InetAddress.getByName("203.0.113.1") | ||
val ipv6 = InetAddress.getByName("2001:db8:ffff:ffff:ffff:ffff:ffff:1") | ||
val unresolvableIpv4 = InetAddress.getByName("198.51.100.1") | ||
val unresolvableIpv6 = InetAddress.getByName("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff") | ||
|
||
val refusedStream = MockResponse( | ||
socketPolicy = SocketPolicy.RESET_STREAM_AT_START, | ||
|
@@ -66,7 +69,6 @@ class RouteFailureTest { | |
this.server1 = server | ||
this.server2 = server2 | ||
|
||
socketFactory = SpecificHostSocketFactory(InetSocketAddress(server.hostName, server.port)) | ||
|
||
client = clientTestRule.newClientBuilder() | ||
.dns(dns) | ||
|
@@ -75,66 +77,11 @@ class RouteFailureTest { | |
.build() | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostOneGoodNoRetryOnConnectionFailure() { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
||
server1.enqueue(refusedStream) | ||
server2.enqueue(bodyResponse) | ||
|
||
dns[server1.hostName] = listOf(ipv6, ipv4) | ||
socketFactory[ipv6] = server1.inetSocketAddress | ||
socketFactory[ipv4] = server2.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(false) | ||
.apply { | ||
retryOnConnectionFailure = false | ||
} | ||
.build() | ||
|
||
executeSynchronously(request) | ||
.assertFailureMatches("stream was reset: REFUSED_STREAM") | ||
|
||
assertThat(client.routeDatabase.failedRoutes).isEmpty() | ||
assertThat(server1.requestCount).isEqualTo(1) | ||
assertThat(server2.requestCount).isEqualTo(0) | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostOneGoodRetryOnConnectionFailure() { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
||
server1.enqueue(refusedStream) | ||
server1.enqueue(refusedStream) | ||
server2.enqueue(bodyResponse) | ||
|
||
dns[server1.hostName] = listOf(ipv6, ipv4) | ||
socketFactory[ipv6] = server1.inetSocketAddress | ||
socketFactory[ipv4] = server2.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(false) | ||
.apply { | ||
retryOnConnectionFailure = true | ||
} | ||
.build() | ||
|
||
executeSynchronously(request) | ||
.assertBody("body") | ||
|
||
assertThat(client.routeDatabase.failedRoutes).isEmpty() | ||
// TODO check if we expect a second request to server1, before attempting server2 | ||
assertThat(server1.requestCount).isEqualTo(2) | ||
assertThat(server2.requestCount).isEqualTo(1) | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostOneGoodNoRetryOnConnectionFailureFastFallback() { | ||
@ParameterizedTest | ||
@ValueSource(booleans = [true, false]) | ||
fun http2OneBadHostOneGoodNoRetryOnConnectionFailure( | ||
fastFallback: Boolean | ||
) { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
@@ -147,7 +94,7 @@ class RouteFailureTest { | |
socketFactory[ipv4] = server2.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(true) | ||
.fastFallback(fastFallback) | ||
.apply { | ||
retryOnConnectionFailure = false | ||
} | ||
|
@@ -161,8 +108,11 @@ class RouteFailureTest { | |
assertThat(server2.requestCount).isEqualTo(0) | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostOneGoodRetryOnConnectionFailureFastFallback() { | ||
@ParameterizedTest | ||
@ValueSource(booleans = [true, false]) | ||
fun http2OneBadHostOneGoodRetryOnConnectionFailure( | ||
fastFallback: Boolean | ||
) { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
@@ -176,7 +126,7 @@ class RouteFailureTest { | |
socketFactory[ipv4] = server2.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(true) | ||
.fastFallback(fastFallback) | ||
.apply { | ||
retryOnConnectionFailure = true | ||
} | ||
|
@@ -191,8 +141,11 @@ class RouteFailureTest { | |
assertThat(server2.requestCount).isEqualTo(1) | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostRetryOnConnectionFailure() { | ||
@ParameterizedTest | ||
@ValueSource(booleans = [true, false]) | ||
fun http2OneBadHostRetryOnConnectionFailure( | ||
fastFallback: Boolean | ||
) { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
@@ -204,7 +157,7 @@ class RouteFailureTest { | |
socketFactory[ipv6] = server1.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(false) | ||
.fastFallback(fastFallback) | ||
.apply { | ||
retryOnConnectionFailure = true | ||
} | ||
|
@@ -217,30 +170,34 @@ class RouteFailureTest { | |
assertThat(server1.requestCount).isEqualTo(1) | ||
} | ||
|
||
@Test | ||
fun http2OneBadHostRetryOnConnectionFailureFastFallback() { | ||
@ParameterizedTest | ||
@ValueSource(booleans = [true, false]) | ||
fun http2OneUnresolvableHost( | ||
fastFallback: Boolean | ||
) { | ||
enableProtocol(Protocol.HTTP_2) | ||
|
||
val request = Request(server1.url("/")) | ||
|
||
server1.enqueue(refusedStream) | ||
server1.enqueue(refusedStream) | ||
server1.enqueue(bodyResponse) | ||
|
||
dns[server1.hostName] = listOf(ipv6) | ||
dns[server1.hostName] = listOf(unresolvableIpv6, ipv6) | ||
// Will remain unresolvable | ||
// socketFactory[unresolvableIpv6] = unresolvableIpv6 | ||
socketFactory[ipv6] = server1.inetSocketAddress | ||
|
||
client = client.newBuilder() | ||
.fastFallback(true) | ||
.fastFallback(fastFallback) | ||
.apply { | ||
retryOnConnectionFailure = true | ||
} | ||
.build() | ||
|
||
executeSynchronously(request) | ||
.assertFailureMatches("stream was reset: REFUSED_STREAM") | ||
.assertCode(200) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ooooh does your fix cause this to connect to a good host? That is PRETTY RAD |
||
|
||
assertThat(client.routeDatabase.failedRoutes).isEmpty() | ||
assertThat(server1.requestCount).isEqualTo(1) | ||
val failedRoutes = client.routeDatabase.failedRoutes | ||
assertThat(failedRoutes.single().socketAddress.address).isEqualTo(unresolvableIpv6) | ||
} | ||
|
||
private fun enableProtocol(protocol: Protocol) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will also capture cases where the call was canceled. Hmm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And looking at the other call to routeDatabase.failed(), I think we should notify the proxy selector.
This is from
RealConnection.kt
:Perhaps the nicest structure is to move that snippet out of
RealConnection
and intoRouteDatabase
, so the two are always in sync.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I wanted this to prompt discussion. I'm not clear that the current exception filtering is obviously right.
I'll split it out and use the opportunity to make a test.