Skip to content

Conversation

@dave-r12
Copy link
Collaborator

@dave-r12
Copy link
Collaborator Author

There is a (extremely rare?) scenario where we may make a connection to a server that is then flagged as unhealthy. Currently 4.4.0 and master will throw a NoSuchElementException.

I ran this test against 4.3.1 and we appear to go an infinite loop. I'm not sure the right action to take in this scenario. Let me know your thoughts.

@yschimke yschimke added this to the 4.5 milestone Mar 22, 2020
@yschimke
Copy link
Collaborator

Not sure about the right fix. Seems like worth fixing in 4.5, probably commit this behind @ignore first?

@swankjesse
Copy link
Collaborator

Great test

@yschimke
Copy link
Collaborator

yschimke commented Mar 24, 2020

This PR is the hit sequel to

image

@dave-r12 dave-r12 changed the title Add test for connection that goes immediately unhealthy. Give up when ExchangeFinder exhausts all routes. Mar 25, 2020

// Make sure we have some routes left to try. One example where we may exhaust all the routes
// would happen if we made a new connection and it immediately is detected as unhealthy.
synchronized(connectionPool) {
Copy link
Collaborator

@yschimke yschimke Mar 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put a

    connectionPool.assertThreadDoesntHoldLock()

At the top of this method? I find it really really useful to document the current expected state of locks whenever we then open into a lock.

// Make sure we have some routes left to try. One example where we may exhaust all the routes
// would happen if we made a new connection and it immediately is detected as unhealthy.
synchronized(connectionPool) {
val routesLeft = routeSelection?.hasNext() ?: true
Copy link
Collaborator

@yschimke yschimke Mar 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like line 108 must have made routeSelection non null, so can we change to !!

Scratch that, I'm less sure of that.

n.b. it passes tests when both are !!, so we are either

a) safe to make 127 and 128 !!
b) missing test coverage

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will be null if the call had a previous connection we could use. Here's a test that demonstrates it:

    server.enqueue(new MockResponse().setResponseCode(401));
    server.enqueue(new MockResponse().setResponseCode(200));
    AtomicReference<Connection> ref = new AtomicReference<>();
    EventListener listener = new EventListener() {
      @Override public void connectionAcquired(Call call, Connection connection) {
        ref.set(connection);
      }
    };

    client = client.newBuilder()
        .eventListener(listener)
        .authenticator((route, response) -> {
          Util.closeQuietly(ref.get().socket());
          return response.request();
        })
        .build();

    executeSynchronously("/").assertCode(200);

val routesLeft = routeSelection?.hasNext() ?: true
val routesSelectionLeft = routeSelector?.hasNext() ?: true
if (nextRouteToTry == null && !routesLeft && !routesSelectionLeft) {
throw IOException("exhausted all routes")
Copy link
Collaborator

@yschimke yschimke Mar 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably be more expressive in this error, it's an interesting case when routes are immediately unhealthy. Can we log the success and error count maybe?

@yschimke
Copy link
Collaborator

Nice PR!

Copy link
Collaborator

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome PR.

synchronized(connectionPool) {
val routesLeft = routeSelection?.hasNext() ?: true
val routesSelectionLeft = routeSelector?.hasNext() ?: true
if (nextRouteToTry == null && !routesLeft && !routesSelectionLeft) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d be tempted to change this to short circuit more aggressively?

      // Make sure we have some routes left to try. One example where we may exhaust all the routes
      // would happen if we made a new connection and it immediately is detected as unhealthy.
      synchronized(connectionPool) {
        if (nextRouteToTry != null) continue

        val routesLeft = routeSelection?.hasNext() ?: true
        if (routesLeft) continue

        val routesSelectionLeft = routeSelector?.hasNext() ?: true
        if (routesSelectionLeft) continue

        throw IOException("exhausted all routes")
      }

@swankjesse
Copy link
Collaborator

I’m gonna merge this as-is because it fixes a big bug, and is already great.

Please address feedback in follow-up PRs as necessary!

@Vinod217
Copy link

Hi @swankjesse & others, We are also facing this issue with okhttp3 3.12.8 version, Could you please confirm if this issue fixed in any latest okhttp3 version?

okhttp3- 3.12.8
Retrofit- 2.6.1

Logs:-
Fatal Exception: java.lang.NoSuchMethodError
No virtual method ,??Վ�蕚õ0ޅ�V��BsK촜ϯ��+Г� ?3??_S?#*烔H奼�cMݗ�Ӷɞͷtey�Nԣݛ?java/net/URI;)Ljava/util/List; in class Ljava/net/ProxySelector; or its super classes (declaration of 'java.net.ProxySelector' appears in /system/framework/core-oj.jar)
okhttp3.internal.connection.RouteSelector.resetNextProxy (RouteSelector.java:129)
okhttp3.internal.connection.RouteSelector. (RouteSelector.java:63)
okhttp3.internal.connection.StreamAllocation. (StreamAllocation.java:101)
okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept (RetryAndFollowUpInterceptor.java:113)
okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.java:147)
okhttp3.internal.http.RealInterceptorChain.proceed (RealInterceptorChain.java:121)
okhttp3.RealCall.getResponseWithInterceptorChain (RealCall.java:257)

Thanks in advance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants