-
Notifications
You must be signed in to change notification settings - Fork 5.9k
8340182: Java HttpClient does not follow default retry limit of 3 retries #25490
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
base: master
Are you sure you want to change the base?
Conversation
Hi @p-nima, welcome to this OpenJDK project and thanks for contributing! We do not recognize you as Contributor and need to ensure you have signed the Oracle Contributor Agreement (OCA). If you have not signed the OCA, please follow the instructions. Please fill in your GitHub username in the "Username" field of the application. Once you have signed the OCA, please let us know by writing If you already are an OpenJDK Author, Committer or Reviewer, please click here to open a new issue so that we can record that fact. Please use "Add GitHub user p-nima" as summary for the issue. If you are contributing this work on behalf of your employer and your employer has signed the OCA, please let us know by writing |
❗ This change is not yet ready to be integrated. |
/covered |
Thank you! Please allow for a few business days to verify that your employer has signed the OCA. Also, please note that pull requests that are pending an OCA check will not usually be evaluated, so your patience is appreciated! |
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
|
||
import static jdk.test.lib.Asserts.assertEquals; |
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.
minor: Did you mean import static org.junit.jupiter.api.Assertions.assertEquals;
here, as you're using junit?
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.
Thank you for your review. The changes have been made in 18bac9f
public class HttpClientRetryLimitTest implements HttpServerAdapters { | ||
|
||
private static final int DEFAULT_RETRY_LIMIT = 3; | ||
private final int retryLimit = Integer.getInteger("jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT); |
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.
Nit: This feels a bit confusing to me when reading it the first time, why not have a retry limit of 1 or 0 as a default and then specify if you want more retries in @test
?
I think it might be a bit easier to read, but if you want to keep it, it's fine with me.
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 line is grabbing the value of the jdk.httpclient.auth.retrylimit
system property, and if it hasn't been set then it defaults to 3. This is based on AuthenticationFilter.java
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.
A comment has been added for it in cdf7812
* @library /test/lib /test/jdk/java/net/httpclient/lib | ||
* @build jdk.httpclient.test.lib.http2.Http2TestServer | ||
* @run junit HttpClientRetryLimitTest | ||
* @run junit/othervm -Djdk.httpclient.auth.retrylimit=1 HttpClientRetryLimitTest |
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.
Do you think adding a retrylimit=0 would be beneficial too? This way every scenario would be covered
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.
Added additional test case scenarios in 18bac9f
|
||
import static jdk.test.lib.Asserts.assertEquals; | ||
|
||
public class HttpClientRetryLimitTest implements HttpServerAdapters { |
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.
Nit: JUnit recommends the following
It is generally recommended to omit the public modifier for test classes, test methods, and lifecycle methods unless there is a technical reason for doing so ...
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.
Thank you for your feedback. The public modifier has been discarded in 18bac9f
public class HttpClientRetryLimitTest implements HttpServerAdapters { | ||
|
||
private static final int DEFAULT_RETRY_LIMIT = 3; | ||
private final int retryLimit = Integer.getInteger("jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT); |
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 can be static
too.
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.
Updated this in 18bac9f
|
||
private static final int DEFAULT_RETRY_LIMIT = 3; | ||
private final int retryLimit = Integer.getInteger("jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT); | ||
private int countRetries; |
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 should better be converted to a local variable.
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.
Discarded the above local variable and introduced a AtomicInteger variable in 18bac9f
httpTestServer.addHandler(httpTestHandler, "/"); | ||
httpTestServer.start(); | ||
|
||
countRetries = 0; |
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.
IMHO, this should be an Atomic{Integer,Long}
due to the possibility of concurrent updates.
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.
Agreed. This has been updated in 18bac9f
t.sendResponseHeaders(401,0); | ||
}; | ||
|
||
httpTestServer.addHandler(httpTestHandler, "/"); |
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.
In the past, there has been occasions in CI where a test server received connections from a totally unrelated test running in parallel on the same host. To avoid such mishaps, we better salt the path a bit by, say, appending the class' simple name?
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.
Thanks for the catch - the class simple name has been appended in 18bac9f
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.
@p-nima, the handler still accepts all calls to /
, you only salted the request URI. Would you mind doing the same in httpTestServer.addHandler(...)
too, please? (You better create a String requestPath
variable and use it both at the handler and the request.)
.uri(new URI("http://" + httpTestServer.serverAuthority() + "/")) | ||
.build(); | ||
|
||
client.send(request, HttpResponse.BodyHandlers.ofString()); |
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.
Since we're not interested in the response, BodyHandlers.discarding()
might be a better fit here.
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.
Updated this in 18bac9f
@Test | ||
public void testDefaultSystemProperty() throws Exception { | ||
|
||
try (HttpTestServer httpTestServer = HttpTestServer.create(HttpClient.Version.HTTP_1_1)) { |
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.
Don't we need to verify the behavior for HTTP/2 too? If so, can we use a @ParameterizedTest
instead and receive the protocol version? (There are several examples you can get inspired from; HttpResponseConnectionLabelTest
, EmptyAuthenticate
, etc.)
} catch (Exception e) { | ||
assertEquals(retryLimit, countRetries, | ||
"Expected number of retries was " + retryLimit + " but actual was "+countRetries); | ||
e.printStackTrace(); | ||
} |
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.
AFAIU, you should
- Wrap
client::send
withassertThrows
- Verify the returned exception
- Then
assertEquals
the retry count
Otherwise, if client::send
unexpectedly doesn't throw anything, your test will incorrectly succeed.
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.
Yes, agreed - the changes have been made in 18bac9f
* @library /test/lib /test/jdk/java/net/httpclient/lib | ||
* @build jdk.httpclient.test.lib.http2.Http2TestServer | ||
* @run junit HttpClientRetryLimitTest | ||
* @run junit/othervm -Djdk.httpclient.auth.retrylimit=1 HttpClientRetryLimitTest |
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.
@dfuch, shall we test against zero and negative values too? (Both are accepted by AuthenticationFilter
as valid.)
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.
We could. We could also throw in garbage to see what happens.
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.
New test scenarios have been added in 18bac9f. We have default, positive, zero and negative values.
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.
@p-nima, thanks for quickly addressing the review feedback. I've dropped some further remarks.
t.sendResponseHeaders(401,0); | ||
}; | ||
|
||
httpTestServer.addHandler(httpTestHandler, "/"); |
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.
@p-nima, the handler still accepts all calls to /
, you only salted the request URI. Would you mind doing the same in httpTestServer.addHandler(...)
too, please? (You better create a String requestPath
variable and use it both at the handler and the request.)
|
||
HttpRequest request = HttpRequest.newBuilder() | ||
.GET() | ||
.uri(new URI("http://" + httpTestServer.serverAuthority() + "/" + this.getClass().getSimpleName() + "/")) |
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.
To ensure the client will fire the request using the protocol version of our preference, could you also pass version
to the request builder too, please?
httpTestServer.start(); | ||
try ( | ||
HttpClient client = HttpClient.newBuilder() | ||
.authenticator(new Authenticator() { |
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.
To ensure the client will fire the request using the protocol version of our preference, could you also pass version
to the client builder too, please?
throw assertThrows(IOException.class, () -> client.send(request, HttpResponse.BodyHandlers.discarding())); | ||
} catch (Exception e) { | ||
if (RETRY_LIMIT > 0){ | ||
assertEquals(RETRY_LIMIT, requestCount.get(), | ||
"Expected number of request retries was " + RETRY_LIMIT + " but actual was "+requestCount); | ||
} | ||
else{ | ||
requestCount.decrementAndGet(); | ||
assertEquals(0, requestCount.get(), | ||
"Expected number of request retries was 0 but actual was "+requestCount); | ||
} | ||
e.printStackTrace(); | ||
} |
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.
AFAICT, you should be using assertThrows
as follows:
IOException exception = assertThrows(...);
assertEquals(exception.message(), "too many authentication attempts. Limit: " + RETRY_LIMIT);
assertEquals(requestCount.get(), RETRY_LIMIT > 0 ? RETRY_LIMIT : 1);
|
||
@ParameterizedTest | ||
@MethodSource("args") | ||
public void testDefaultSystemProperty(HttpClient.Version version) throws Exception { |
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.
I see you made the class package-private in 18bac9f. You could have additionally made the method package-private too.
private static final int RETRY_LIMIT = Integer.getInteger( | ||
"jdk.httpclient.auth.retrylimit", DEFAULT_RETRY_LIMIT); | ||
|
||
static Stream<HttpClient.Version> args() { |
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.
I think we should also test against with SSL and without SSL cases. See HttpResponseLimitingTest.ServerClientPair
for inspiration.
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
|
||
class HttpClientRetryLimitTest implements HttpServerAdapters { |
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.
Shall we rename this to HttpClientAuthRetryLimit
, since there are several other retry limits?
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.
Sounds reasonable to rename - and fix the @summary
in the jtreg headers too
/touch |
@jaikiran The pull request is being re-evaluated and the inactivity timeout has been reset. |
The AuthenticationFilter did not respect the default retry limit of 3 retries in case of invalid credentials supplied.
This PR helps to resolve the bug and tests it with default and updated retry limit set via
jdk.httpclient.auth.retrylimit=1
.The test is green with tiers 1, 2, 3 and the test is stable.
Progress
Error
Issue
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/25490/head:pull/25490
$ git checkout pull/25490
Update a local copy of the PR:
$ git checkout pull/25490
$ git pull https://git.openjdk.org/jdk.git pull/25490/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 25490
View PR using the GUI difftool:
$ git pr show -t 25490
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/25490.diff