Skip to content

Commit

Permalink
Merge pull request #208 from yrodiere/fix-secondary-limit
Browse files Browse the repository at this point in the history
Retry search requests upon hitting secondary rate limits
  • Loading branch information
yrodiere authored Jan 15, 2025
2 parents 4e5c44f + 1088892 commit 25ae31f
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ nb-configuration.xml

# Local environment
.env
.quarkus
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.github.lottery.config;

import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

@ConfigMapping(prefix = "lottery")
public interface DeploymentConfig {

@WithDefault("false")
boolean dryRun();

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.function.Predicate;
import java.util.stream.Stream;

import io.quarkus.github.lottery.config.DeploymentConfig;
import org.kohsuke.github.GHDirection;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHIssueComment;
Expand Down Expand Up @@ -55,6 +56,7 @@
*/
public class GitHubRepository implements AutoCloseable {

private final DeploymentConfig deploymentConfig;
private final Clock clock;
private final GitHubClientProvider clientProvider;
private final GitHubConfigFileProvider configFileProvider;
Expand All @@ -66,8 +68,10 @@ public class GitHubRepository implements AutoCloseable {
private GHRepository repository;
private DynamicGraphQLClient graphQLClient;

public GitHubRepository(Clock clock, GitHubClientProvider clientProvider, GitHubConfigFileProvider configFileProvider,
public GitHubRepository(DeploymentConfig deploymentConfig, Clock clock,
GitHubClientProvider clientProvider, GitHubConfigFileProvider configFileProvider,
MessageFormatter messageFormatter, GitHubRepositoryRef ref) {
this.deploymentConfig = deploymentConfig;
this.clock = clock;
this.clientProvider = clientProvider;
this.configFileProvider = configFileProvider;
Expand Down Expand Up @@ -337,6 +341,12 @@ public boolean isClosed() throws IOException {
*/
public void update(String topicSuffix, String markdownBody, boolean comment)
throws IOException {
if (deploymentConfig.dryRun()) {
Log.infof("[DRY RUN] Topic update:\n\tTopic:%s\n\tSuffix:%s\n\tBody:%s\n\tComment:%s",
ref, topicSuffix, markdownBody, comment);
return;
}

var dedicatedIssue = getDedicatedIssues().findFirst();
if (ref.expectedSuffixStart() != null && !topicSuffix.startsWith(ref.expectedSuffixStart())
|| ref.expectedSuffixStart() == null && !topicSuffix.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.time.Clock;
import java.util.ArrayList;
import java.util.List;

import io.quarkus.github.lottery.config.DeploymentConfig;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

Expand All @@ -18,6 +20,8 @@
@ApplicationScoped
public class GitHubService {

@Inject
DeploymentConfig deploymentConfig;
@Inject
Clock clock;
@Inject
Expand All @@ -44,6 +48,6 @@ public List<GitHubRepositoryRef> listRepositories() throws IOException {
}

public GitHubRepository repository(GitHubRepositoryRef ref) {
return new GitHubRepository(clock, clientProvider, configFileProvider, messageFormatter, ref);
return new GitHubRepository(deploymentConfig, clock, clientProvider, configFileProvider, messageFormatter, ref);
}
}
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;
}

}
6 changes: 3 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ quarkus.info.enabled=true
%test.quarkus.log.category."io.quarkus.github.lottery".level=TRACE

%dev.quarkus.scheduler.enabled=false
%dev.quarkus.log.min-level=TRACE
%dev.quarkus.log.category."io.quarkus.github.lottery".level=TRACE
%dev.quarkus.log.category."org.kohsuke.github.GitHubClient".level=TRACE
%dev.quarkus.log.min-level=FINEST
%dev.quarkus.log.category."io.quarkus.github.lottery".level=FINEST
%dev.quarkus.log.category."org.kohsuke.github.GitHubClient".level=FINEST

%prod.quarkus.openshift.labels."app"=quarkus-github-lottery
# Renew the SSL certificate automatically
Expand Down

0 comments on commit 25ae31f

Please sign in to comment.