Add Retry Strategy Implementations and Tests#1
Add Retry Strategy Implementations and Tests#1nishkarsh-db wants to merge 3 commits intoUnified_Retry_1from
Conversation
- Add IRetryStrategy interface defining retry decision contract - Add IdempotentRetryStrategy for safe-to-retry operations (GET, metadata) - Add NonIdempotentRetryStrategy for conservative retry (POST, mutations) - Add comprehensive test coverage (16 tests total: 8 per strategy) - Expand RetryUtils with helper methods for strategies IdempotentRetryStrategy retries on 5xx, 429, 408, and network errors. NonIdempotentRetryStrategy only retries 503/429 with Retry-After headers. Both integrate with RetryTimeoutManager for bounded retry attempts. Co-authored-by: Cursor <cursoragent@cursor.com>
- Check if status code is in ApiRetriableHttpCodes from connection context - If API retriable code OR retriable by standard logic, proceed with retry - Pass isApiRetriableCode boolean to RetryTimeoutManager.evaluateRetryTimeoutForResponse() - Timeout manager uses flag to deduct from correct budget (API codes vs standard) - Allows custom status codes (e.g., 404, 400) to have independent retry budget Test updates: - Add lenient mock for getApiRetriableHttpCodes() in setUp - Update evaluateRetryTimeoutForResponse mocks to include boolean parameter - All 16 tests pass (8 IdempotentRetryStrategy + 8 NonIdempotentRetryStrategy) Co-authored-by: Cursor <cursoragent@cursor.com>
- Move common retry logic documentation to IRetryStrategy interface - Simplify IdempotentRetryStrategy docs to focus on implementation specifics - Simplify NonIdempotentRetryStrategy docs to focus on implementation specifics - Each concrete class now documents only what makes it unique: * Which status codes/exceptions are retriable * Retry delay calculation approach (Retry-After vs exponential backoff) * Special requirements (e.g., Retry-After header requirement for non-idempotent) - All 16 tests still pass Co-authored-by: Cursor <cursoragent@cursor.com>
| * </ul> | ||
| * | ||
| * <p><b>Critical Requirement:</b> Retry-After header MUST be present. Will NOT retry without it | ||
| * (except for API retriable codes which may use exponential backoff). |
There was a problem hiding this comment.
don't think we're actually following this?
There was a problem hiding this comment.
There are no tests that cover API retriable codes for either strategy
| private static final int MIN_BACKOFF_INTERVAL_MILLISECONDS = 1000; // 1s | ||
| private static final int MAX_BACKOFF_INTERVAL_MILLISECONDS = 10000; // 10s | ||
| private static final String RETRY_AFTER_HEADER = "Retry-After"; | ||
| private static final Random RANDOM = new Random(); |
There was a problem hiding this comment.
Multiple JDBC connections retrying concurrently will contend on the shared Random seed, producing poor randomness and potential performance degradation.
Use ThreadLocalRandom.current().nextDouble() instead.
| if (response.containsHeader(RETRY_AFTER_HEADER)) { | ||
| try { | ||
| int retryAfterSeconds = | ||
| Integer.parseInt(response.getFirstHeader(RETRY_AFTER_HEADER).getValue().trim()); |
There was a problem hiding this comment.
should we add some safe guard to ensure that whatever we parse is > 0 i.e. we do not want to subtract time, or retry instantly. so maybe do a max(some_default, retryAfter)
| * @return Optional containing the retry interval in milliseconds, or empty if header is missing | ||
| * or invalid | ||
| */ | ||
| public static Optional<Integer> extractRetryAfterHeader(Map<String, String> headers) { |
There was a problem hiding this comment.
can we dedup the logic for these two methods and make one use the other?
There was a problem hiding this comment.
there is some duplication between the two strategies, is there scope to perhaps define an abstract base class?
|
|
||
| int retryAfter = RetryUtils.calculateExponentialBackoff(executionAttempt); | ||
| if (!retryTimeoutManager.evaluateRetryTimeoutForException(retryAfter)) { | ||
| LOGGER.error( |
There was a problem hiding this comment.
don't think this should be error. same for the other strategy
| return Optional.empty(); | ||
| } | ||
|
|
||
| String retryAfterValue = headers.get(RETRY_AFTER_HEADER); |
There was a problem hiding this comment.
also, this is case sensitive, which should not be the case
| return isRetriable; | ||
| } | ||
|
|
||
| private boolean isExceptionRetrieable(Exception e) { |
| return isRetriable; | ||
| } | ||
|
|
||
| private boolean isExceptionRetrieable(Exception e) { |
32e2032 to
98a6f6d
Compare
Description
Introduces the retry strategy implementations that consume the foundation components from PR1:
IRetryStrategy interface: Defines contract for retry decision logic with two methods:
shouldRetryAfter()for HTTP responses - returns retry delay and checks timeout budgetsshouldRetryAfter()for exceptions - returns retry delay for network errorsIdempotentRetryStrategy: Aggressive retry strategy for safe operations
NonIdempotentRetryStrategy: Conservative retry strategy for unsafe operations
API Retriable Codes Integration:
connectionContext.getApiRetriableHttpCodes()isApiRetriableCodeflag toRetryTimeoutManagerfor correct budget deductionRetryUtils enhancements:
calculateExponentialBackoff()- exponential backoff with jitter (1s to 10s)extractRetryAfterHeader()- parses Retry-After header from responsesgetRetryStrategy()- returns appropriate strategy based on RequestTypethrowDatabricksHttpException()- converts exceptions to standardized formatTesting
All tests pass (16 tests):
[INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0IdempotentRetryStrategyTest (8 tests):
NonIdempotentRetryStrategyTest (8 tests):
Notes for Reviewer