Skip to content

Commit f79bc62

Browse files
Copilotbaywet
andcommitted
fix: handle negative retry interval in RetryHandler
- Add check for negative retry interval in retryRequest method - Set random delay between 1-10ms when retry interval is negative - Add unit tests for negative retry interval scenarios - Prevents IllegalArgumentException: timeout value is negative Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
1 parent a903113 commit f79bc62

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

components/http/okHttp/src/main/java/com/microsoft/kiota/http/middleware/RetryHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ && isBuffered(request)
110110

111111
if (shouldRetry) {
112112
long retryInterval = getRetryAfter(response, retryOption.delay(), executionCount);
113+
// Ensure minimum delay if retry interval is negative
114+
if (retryInterval < 0) {
115+
retryInterval = 1 + (long) (Math.random() * 9); // Random delay between 1-10ms
116+
}
113117
span.setAttribute(HTTP_REQUEST_RESEND_DELAY, Math.round(retryInterval / 1000f));
114118
try {
115119
Thread.sleep(retryInterval);

components/http/okHttp/src/test/java/com/microsoft/kiota/http/middleware/RetryHandlerTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
package com.microsoft.kiota.http.middleware;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.when;
7+
8+
import com.microsoft.kiota.http.middleware.options.RetryHandlerOption;
9+
10+
import io.opentelemetry.api.trace.Span;
11+
12+
import okhttp3.Request;
13+
import okhttp3.Response;
414

515
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.Test;
617
import org.junit.jupiter.params.ParameterizedTest;
718
import org.junit.jupiter.params.provider.Arguments;
819
import org.junit.jupiter.params.provider.MethodSource;
@@ -58,4 +69,73 @@ void testTryParseTimeHeader(String headerValue, double expectedDelay) {
5869
assertEquals(
5970
expectedDelay, result, DELTA, "Failed for header value: '" + headerValue + "'");
6071
}
72+
73+
@Test
74+
void testGetRetryAfterReturnsNegativeWhenHeaderParsingFails() {
75+
// Create a mock response with a Retry-After header that will fail parsing
76+
Response mockResponse = mock(Response.class);
77+
when(mockResponse.header("Retry-After")).thenReturn("invalid");
78+
when(mockResponse.code()).thenReturn(429);
79+
80+
// getRetryAfter should return -1 when parsing fails but header exists
81+
long retryAfter = retryHandler.getRetryAfter(mockResponse, 3, 1);
82+
assertEquals(-1, retryAfter, "Expected -1 when Retry-After header parsing fails");
83+
}
84+
85+
@Test
86+
void testRetryRequestHandlesNegativeRetryInterval() throws Exception {
87+
// Create mocks
88+
Response mockResponse = mock(Response.class);
89+
Request mockRequest = mock(Request.class);
90+
RetryHandlerOption mockOption = mock(RetryHandlerOption.class);
91+
Span mockSpan = mock(Span.class);
92+
93+
// Setup mock behaviors
94+
when(mockResponse.code()).thenReturn(429);
95+
when(mockResponse.header("Retry-After")).thenReturn("invalid");
96+
when(mockRequest.method()).thenReturn("GET");
97+
when(mockRequest.body()).thenReturn(null);
98+
when(mockOption.maxRetries()).thenReturn(3);
99+
when(mockOption.delay()).thenReturn(3L);
100+
when(mockOption.shouldRetry())
101+
.thenReturn(
102+
(delay, executionCount, request, response) ->
103+
true); // Always retry for this test
104+
105+
// Call retryRequest - it should not throw IllegalArgumentException
106+
// The main goal is to verify no exception is thrown when retry interval is negative
107+
boolean result =
108+
retryHandler.retryRequest(mockResponse, 1, mockRequest, mockOption, mockSpan);
109+
110+
// Verify the method returned true (should retry)
111+
assertTrue(result, "Expected retryRequest to return true");
112+
}
113+
114+
@Test
115+
void testRetryRequestWithNegativeRetryIntervalAppliesRandomDelay() throws Exception {
116+
// Create mocks
117+
Response mockResponse = mock(Response.class);
118+
Request mockRequest = mock(Request.class);
119+
RetryHandlerOption mockOption = mock(RetryHandlerOption.class);
120+
Span mockSpan = mock(Span.class);
121+
122+
// Setup mock behaviors to force negative retry interval
123+
when(mockResponse.code()).thenReturn(503);
124+
when(mockResponse.header("Retry-After")).thenReturn("invalid");
125+
when(mockRequest.method()).thenReturn("POST");
126+
when(mockRequest.body()).thenReturn(null);
127+
when(mockOption.maxRetries()).thenReturn(5);
128+
when(mockOption.delay()).thenReturn(1L);
129+
when(mockOption.shouldRetry())
130+
.thenReturn((delay, executionCount, request, response) -> true);
131+
132+
// Test multiple times to verify no exception is thrown
133+
// This verifies the fix for negative retry intervals
134+
for (int i = 0; i < 5; i++) {
135+
boolean result =
136+
retryHandler.retryRequest(mockResponse, 1, mockRequest, mockOption, mockSpan);
137+
// Verify each call succeeds without throwing IllegalArgumentException
138+
assertTrue(result, "Expected retryRequest to return true on iteration " + i);
139+
}
140+
}
61141
}

0 commit comments

Comments
 (0)