Skip to content

Commit 25eee97

Browse files
committed
Correct logic error in waiting, and do not assume server time is in sync with local machine time
1 parent 338de9a commit 25eee97

File tree

4 files changed

+24
-9
lines changed

4 files changed

+24
-9
lines changed

src/main/java/org/kohsuke/github/AbuseLimitHandler.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.IOException;
66
import java.io.InterruptedIOException;
77
import java.net.HttpURLConnection;
8+
import java.time.Duration;
89
import java.time.ZonedDateTime;
910
import java.time.format.DateTimeFormatter;
1011
import java.time.temporal.ChronoUnit;
@@ -25,6 +26,8 @@
2526
@Deprecated
2627
public abstract class AbuseLimitHandler extends GitHubAbuseLimitHandler {
2728

29+
private static final int MINIMUM_ABUSE_RETRY_MILLIS = 1000;
30+
2831
/**
2932
* Called when the library encounters HTTP error indicating that the API abuse limit is reached.
3033
*
@@ -110,15 +113,26 @@ long parseWaitTime(HttpURLConnection uc) {
110113
String v = uc.getHeaderField("Retry-After");
111114
if (v == null) {
112115
// can't tell, wait for unambiguously over one minute per GitHub guidance
113-
return 61 * 1000;
116+
return Duration.ofSeconds(61).toMillis();
114117
}
115118

116119
try {
117-
return Math.max(1000, Long.parseLong(v) * 1000);
120+
return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, Duration.ofSeconds(Long.parseLong(v)).toMillis());
118121
} catch (NumberFormatException nfe) {
119122
// The retry-after header could be a number in seconds, or an http-date
123+
// We know it was a date if we got a number format exception :)
124+
125+
// Don't use ZonedDateTime.now(), because the local and remote server times may not be in sync
126+
// Instead, we can take advantage of the Date field in the response to see what time the remote server thinks it is
127+
String dateField = uc.getHeaderField("Date");
128+
ZonedDateTime now;
129+
if (dateField != null) {
130+
now = ZonedDateTime.parse(dateField, DateTimeFormatter.RFC_1123_DATE_TIME);
131+
} else {
132+
now = ZonedDateTime.now();
133+
}
120134
ZonedDateTime zdt = ZonedDateTime.parse(v, DateTimeFormatter.RFC_1123_DATE_TIME);
121-
return ChronoUnit.MILLIS.between(ZonedDateTime.now(), zdt);
135+
return Math.max(MINIMUM_ABUSE_RETRY_MILLIS, ChronoUnit.MILLIS.between(now, zdt));
122136
}
123137
}
124138

src/main/java/org/kohsuke/github/GHRateLimit.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ && getRemaining() <= other.getRemaining())) {
527527
return this;
528528
} else if (!(other instanceof UnknownLimitRecord)) {
529529
// If the above is not the case that means other has a later reset
530-
// or the same resent and fewer requests remaining.
530+
// or the same reset and fewer requests remaining.
531531
// If the other record is not an unknown record, the other is more recent
532532
return other;
533533
} else if (this.isExpired() && !other.isExpired()) {

src/test/java/org/kohsuke/github/AbuseLimitHandlerTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.hamcrest.CoreMatchers.notNullValue;
1919
import static org.hamcrest.Matchers.greaterThan;
2020
import static org.hamcrest.core.IsInstanceOf.instanceOf;
21+
import static org.junit.Assert.assertEquals;
2122

2223
// TODO: Auto-generated Javadoc
2324
/**
@@ -476,8 +477,7 @@ public void onError(IOException e, HttpURLConnection uc) throws IOException {
476477
// logic
477478
// Manually invoke it to make sure it's what we intended
478479
long waitTime = parseWaitTime(uc);
479-
// The exact value here will depend on when the test is run, but it should be positive, and huge
480-
assertThat(waitTime, greaterThan(1000 * 1000l));
480+
assertEquals(waitTime, 7*60*1000);
481481

482482
AbuseLimitHandler.FAIL.onError(e, uc);
483483
}
@@ -537,7 +537,8 @@ public void onError(IOException e, HttpURLConnection uc) throws IOException {
537537
// logic
538538
// Manually invoke it to make sure it's what we intended
539539
long waitTime = parseWaitTime(uc);
540-
assertThat(waitTime, greaterThan(60000l));
540+
// We expect to wait for 61s if there's no information from the server
541+
assertEquals(waitTime, 61000l);
541542

542543
AbuseLimitHandler.FAIL.onError(e, uc);
543544
}

src/test/resources/org/kohsuke/github/AbuseLimitHandlerTest/wiremock/testHandler_Wait_Secondary_Limits_Too_Many_Requests_Date_Retry_After/mappings/3-r_h_t_fail.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
"status": 429,
1515
"body": "{\"message\":\"You have exceeded a secondary rate limit. Please wait a few minutes before you try again\"}",
1616
"headers": {
17-
"Date": "{{now timezone='GMT' format='EEE, dd MMM yyyy HH:mm:ss z'}}",
17+
"Date": "Mon, 21 Oct 2024 07:27:00 GMT",
1818
"Content-Type": "application/json; charset=utf-8",
1919
"Server": "GitHub.com",
2020
"Status": "429 Too Many Requests",
21-
"Retry-After": "Mon, 21 Oct 2115 07:28:00 GMT",
21+
"Retry-After": "Mon, 21 Oct 2024 07:34:00 GMT",
2222
"Cache-Control": "private, max-age=60, s-maxage=60",
2323
"Vary": [
2424
"Accept, Authorization, Cookie, X-GitHub-OTP",

0 commit comments

Comments
 (0)