Skip to content

Commit ffdc0d7

Browse files
authored
Merge pull request #196 from jgangemi/jae/pr-trigger-retry
- retry failed requests to retrieve pull-request info
2 parents fc96aae + ffb788f commit ffdc0d7

4 files changed

Lines changed: 163 additions & 6 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.github.kostyasha.github.integration.generic.utils;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.IOException;
7+
8+
/**
9+
* Used to wrap and retry calls to GitHub in the event an error is thrown.
10+
*/
11+
public class RetryableGitHubOperation {
12+
13+
private static final Logger LOG = LoggerFactory.getLogger(RetryableGitHubOperation.class);
14+
15+
public interface GitOperation<T> {
16+
T execute() throws IOException;
17+
}
18+
19+
private RetryableGitHubOperation() {
20+
}
21+
22+
/**
23+
* Executes a GitHub operation up to 3 times with a 2 second delay.
24+
*
25+
* @param operation GitHub operation to execute
26+
* @return result of operation
27+
*/
28+
public static <T> T execute(GitOperation<T> operation) throws IOException {
29+
return execute(3, 2000, operation);
30+
}
31+
32+
public static <T> T execute(int retries, long delay, GitOperation<T> operation) throws IOException {
33+
T result = null;
34+
35+
int count = 0;
36+
while (count++ < retries) {
37+
try {
38+
result = operation.execute();
39+
break;
40+
} catch (IOException e) {
41+
if (count == retries) {
42+
throw e;
43+
}
44+
45+
LOG.debug("Failed retrieving pull request(s), retrying...", e);
46+
sleep(delay);
47+
}
48+
}
49+
50+
return result;
51+
}
52+
53+
private static void sleep(long delay) {
54+
try {
55+
Thread.sleep(delay);
56+
} catch (InterruptedException e) {
57+
Thread.currentThread().interrupt();
58+
LOG.warn("Interrupted while delaying git operation", e);
59+
}
60+
}
61+
}

github-pullrequest-plugin/src/main/java/org/jenkinsci/plugins/github/pullrequest/GitHubPRPullRequest.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import java.util.Collections;
2020
import java.util.Date;
2121
import java.util.HashSet;
22+
import java.util.List;
2223
import java.util.Set;
2324

25+
import static com.github.kostyasha.github.integration.generic.utils.RetryableGitHubOperation.execute;
2426
import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE;
2527
import static org.jenkinsci.plugins.github.pullrequest.utils.ObjectsUtil.isNull;
2628

@@ -69,7 +71,8 @@ public GitHubPRPullRequest(GHPullRequest pr) throws IOException {
6971

7072
try {
7173
Date maxDate = new Date(0);
72-
for (GHIssueComment comment : pr.getComments()) {
74+
List<GHIssueComment> comments = execute(() -> pr.getComments());
75+
for (GHIssueComment comment : comments) {
7376
if (comment.getCreatedAt().compareTo(maxDate) > 0) {
7477
maxDate = comment.getCreatedAt();
7578
}
@@ -81,7 +84,7 @@ public GitHubPRPullRequest(GHPullRequest pr) throws IOException {
8184
}
8285

8386
try {
84-
userEmail = pr.getUser().getEmail();
87+
userEmail = execute(() -> pr.getUser().getEmail());
8588
} catch (Exception e) {
8689
LOGGER.error("Can't get GitHub user email.", e);
8790
userEmail = "";
@@ -90,15 +93,15 @@ public GitHubPRPullRequest(GHPullRequest pr) throws IOException {
9093
GHRepository remoteRepo = pr.getRepository();
9194

9295
try {
93-
updateLabels(remoteRepo.getIssue(number).getLabels());
96+
updateLabels(execute(() -> remoteRepo.getIssue(number).getLabels()));
9497
} catch (IOException e) {
9598
LOGGER.error("Can't retrieve label list: {}", e);
9699
inBadState = true;
97100
}
98101

99102
// see https://github.com/kohsuke/github-api/issues/111
100103
try {
101-
mergeable = pr.getMergeable();
104+
mergeable = execute(() -> pr.getMergeable());
102105
} catch (IOException e) {
103106
LOGGER.error("Can't get mergeable status.", e);
104107
mergeable = false;

github-pullrequest-plugin/src/main/java/org/jenkinsci/plugins/github/pullrequest/GitHubPRTrigger.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import static java.text.DateFormat.getDateTimeInstance;
4545
import static java.util.Collections.emptyList;
4646
import static java.util.Collections.singleton;
47+
import static com.github.kostyasha.github.integration.generic.utils.RetryableGitHubOperation.execute;
4748
import static org.jenkinsci.plugins.github.pullrequest.GitHubPRTriggerMode.LIGHT_HOOKS;
4849
import static org.jenkinsci.plugins.github.pullrequest.trigger.check.BranchRestrictionFilter.withBranchRestriction;
4950
import static org.jenkinsci.plugins.github.pullrequest.trigger.check.LocalRepoUpdater.updateLocalRepo;
@@ -175,6 +176,7 @@ public void run() {
175176

176177

177178
@CheckForNull
179+
@Override
178180
public GitHubPRPollingLogAction getPollingLogAction() {
179181
if (isNull(pollingLogAction) && nonNull(job)) {
180182
pollingLogAction = new GitHubPRPollingLogAction(job);
@@ -319,9 +321,9 @@ private static Set<GHPullRequest> pullRequestsToCheck(@Nullable Integer prNumber
319321
@Nonnull GHRepository remoteRepo,
320322
@Nonnull GitHubPRRepository localRepo) throws IOException {
321323
if (prNumber != null) {
322-
return singleton(remoteRepo.getPullRequest(prNumber));
324+
return execute(() -> singleton(remoteRepo.getPullRequest(prNumber)));
323325
} else {
324-
List<GHPullRequest> remotePulls = remoteRepo.getPullRequests(GHIssueState.OPEN);
326+
List<GHPullRequest> remotePulls = execute(() -> remoteRepo.getPullRequests(GHIssueState.OPEN));
325327

326328
Set<Integer> remotePRNums = from(remotePulls).transform(extractPRNumber()).toSet();
327329

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.github.kostyasha.github.integration.generic.utils;
2+
3+
import com.github.kostyasha.github.integration.generic.utils.RetryableGitHubOperation.GitOperation;
4+
5+
import org.junit.Before;
6+
import org.junit.Test;
7+
import org.mockito.Mock;
8+
import org.mockito.MockitoAnnotations;
9+
10+
import java.io.FileNotFoundException;
11+
import java.io.IOException;
12+
13+
import static org.hamcrest.MatcherAssert.assertThat;
14+
import static org.hamcrest.Matchers.instanceOf;
15+
import static org.hamcrest.Matchers.is;
16+
import static org.mockito.Mockito.times;
17+
import static org.mockito.Mockito.verify;
18+
import static org.mockito.Mockito.when;
19+
20+
public class RetryableGitHubOperationTest {
21+
22+
private Object actualResult;
23+
24+
@Mock
25+
private GitOperation<Object> mockOperation;
26+
27+
private Object mockResult;
28+
29+
private int retries;
30+
31+
private Exception thrownException;
32+
33+
@Before
34+
public void setup() {
35+
MockitoAnnotations.initMocks(this);
36+
}
37+
38+
@Test
39+
public void testNoRetryRequired() throws Exception {
40+
givenOperationSucceeds();
41+
whenExecuteOperation();
42+
thenOperationIsSuccessful();
43+
}
44+
45+
@Test
46+
public void testRetryWasExceeded() throws Exception {
47+
givenOperationExceedsRetry();
48+
whenExecuteOperation();
49+
thenOperationWasNotSuccessful();
50+
}
51+
52+
@Test
53+
public void testRetryWasRequired() throws Exception {
54+
givenOperationRequiresRetry();
55+
whenExecuteOperation();
56+
thenOperationIsSuccessful();
57+
}
58+
59+
private void givenOperationExceedsRetry() throws IOException {
60+
retries = 2;
61+
when(mockOperation.execute()).thenThrow(new FileNotFoundException());
62+
}
63+
64+
private void givenOperationRequiresRetry() throws IOException {
65+
retries = 2;
66+
when(mockOperation.execute()).thenThrow(new FileNotFoundException())
67+
.thenReturn(mockResult);
68+
}
69+
70+
private void givenOperationSucceeds() throws IOException {
71+
retries = 1;
72+
when(mockOperation.execute()).thenReturn(mockResult);
73+
}
74+
75+
private void thenOperationIsSuccessful() throws IOException {
76+
assertThat(actualResult, is(mockResult));
77+
verify(mockOperation, times(retries)).execute();
78+
}
79+
80+
private void thenOperationWasNotSuccessful() {
81+
assertThat(thrownException, instanceOf(FileNotFoundException.class));
82+
}
83+
84+
private void whenExecuteOperation() {
85+
try {
86+
actualResult = RetryableGitHubOperation.execute(retries, 1L, mockOperation);
87+
} catch (Exception e) {
88+
thrownException = e;
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)