Skip to content

Commit

Permalink
Retry search requests upon hitting secondary rate limits
Browse files Browse the repository at this point in the history
  • Loading branch information
yrodiere committed Jan 15, 2025
1 parent 5e3966d commit 1088892
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
72 changes: 72 additions & 0 deletions src/main/java/io/quarkus/github/lottery/util/RetryingIterator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.quarkus.github.lottery.util;

import java.io.InterruptedIOException;
import java.io.UncheckedIOException;
import java.util.Iterator;
import java.util.function.Supplier;

import org.kohsuke.github.GHException;
import org.kohsuke.github.PagedIterator;

import io.quarkus.logging.Log;

// Workaround for https://github.com/hub4j/github-api/issues/2009
class RetryingIterator<T> implements Iterator<T> {
private static final int MAX_RETRY = 3;

// Wait for unambiguously over one minute per GitHub guidance
private static final long DEFAULT_WAIT_MILLIS = 61 * 1000;

private final PagedIterator<T> delegate;

public RetryingIterator(PagedIterator<T> delegate) {
this.delegate = delegate;
}

@Override
public boolean hasNext() {
return doWithRetry(delegate::hasNext);
}

@Override
public T next() {
return doWithRetry(delegate::next);
}

private <T> T doWithRetry(Supplier<T> action) {
RuntimeException rateLimitException = null;
for (int i = 0; i < MAX_RETRY; i++) {
if (rateLimitException != null) {
waitBeforeRetry(rateLimitException);
}
try {
return action.get();
} catch (RuntimeException e) {
if (isSecondaryRateLimitReached(e)) {
if (rateLimitException == null) {
rateLimitException = e;
} else {
rateLimitException.addSuppressed(e);
}
} else {
throw e;
}
}
}
throw rateLimitException;
}

private static boolean isSecondaryRateLimitReached(RuntimeException e) {
return e instanceof GHException
&& e.getCause() != null && e.getCause().getMessage().contains("secondary rate limit");
}

private void waitBeforeRetry(RuntimeException e) {
Log.infof("GitHub API reached a secondary rate limit; waiting %s ms before retrying...", DEFAULT_WAIT_MILLIS);
try {
Thread.sleep(DEFAULT_WAIT_MILLIS);
} catch (InterruptedException ex) {
throw new UncheckedIOException((InterruptedIOException) new InterruptedIOException().initCause(e));
}
}
}
13 changes: 12 additions & 1 deletion src/main/java/io/quarkus/github/lottery/util/Streams.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package io.quarkus.github.lottery.util;

import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.kohsuke.github.GHException;
import org.kohsuke.github.PagedIterable;
import org.kohsuke.github.PagedIterator;

public final class Streams {

Expand Down Expand Up @@ -63,10 +67,17 @@ public T next() {
}

public static <T> Stream<T> toStream(PagedIterable<T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false);
return StreamSupport.stream(spliterator(iterable), false);
}

private static <T> Spliterator<T> spliterator(PagedIterable<T> iterable) {
var pagedIterator = iterable.iterator();
var workaroundIterator = new RetryingIterator<>(pagedIterator);
return Spliterators.spliteratorUnknownSize(workaroundIterator, 0);
}

public static <T> BinaryOperator<T> last() {
return (first, second) -> second;
}

}

0 comments on commit 1088892

Please sign in to comment.