Skip to content

Implement retries in the Jetty transporter - #2242

Open
sakshichitnis27 wants to merge 3 commits into
apache:masterfrom
sakshichitnis27:mresolver-2239-jetty-retry
Open

sakshichitnis27 wants to merge 3 commits into
apache:masterfrom
sakshichitnis27:mresolver-2239-jetty-retry

Conversation

@sakshichitnis27

@sakshichitnis27 sakshichitnis27 commented Oct 28, 2026 •

Copy link
Copy Markdown

Implement retries in the Jetty transporter

The Jetty HttpClient has no retry hook, so JettyTransporter now retries HEAD, GET and PUT requests itself, using the existing aether.connector.http.retryHandler.* settings (count, interval, intervalMax, serviceUnavailable), the same way the Apache and JDK transporters do.

  • Responses with a status in serviceUnavailable (429 and 503 by default) are retried after a linearly growing delay. A Retry-After header (seconds or HTTP date) is used instead if present. The delay is capped at intervalMax.
  • I/O failures (e.g. a connection closed by the server) and timeouts are retried immediately. Failures that are unlikely to go away on their own (unknown host, connection refused, no route, SSL errors) are not retried, which mirrors Apache's DefaultHttpRequestRetryHandler.
  • Cancelled transfers and the redirects refused by InsecureRedirectGuard / SameAuthorityRedirectGuard are not retried. The guards now abort with a small RedirectRefusedException (still an IOException) so they can be told apart.
  • Retries are logged through HttpTransporter.logRetry / logImmediateRetry, like for the other transporters.
  • implPeek now unwraps the ExecutionException like implGet and implPut already do, so a failed peek surfaces the underlying IOException.
  • Removed "Does not support configurable retries" for Jetty from transporter-known-issues.md.

Only the time until the response headers arrive is covered, as for the other transporters. A download that fails halfway through the body is not retried.

Tests:

  • Enabled the retry tests from HttpTransporterTest for Jetty (supportsRetryLogging() and the @Disabled overrides are gone). Before this change 11 of them failed on master.
  • Added JettyRetryHandlerTest (retry limit, non-retriable failures, Retry-After as seconds/date/invalid, delay cap).
  • The shared retry tests only use HEAD, so I added GET and PUT cases to JettyTransporterTest (503 then success, closed connection then success, giving up after the retry count). These fail if retries are switched off.

Verified with mvn -pl maven-resolver-transport-jetty verify (143 tests, 0 failures, 5 skipped as before).

This closes #2239


To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
check one of the following:

Retry HEAD, GET and PUT requests according to the
aether.connector.http.retryHandler.* settings, since the Jetty
HttpClient has no retry hook. Log retries like the other transporters.

This closes apache#2239
@sakshichitnis27
sakshichitnis27 marked this pull request as ready for review October 28, 2026 17:26

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid implementation that brings Jetty to parity with the Apache/JDK transporters. One resource management issue in the GET retry path, plus a minor comment on the Retry-After heuristic.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Comment on lines +134 to +141
if (retryAfter.contains(":")) { // is date when to retry
delay = Math.max(
responseHeaders.getDateField(HttpHeader.RETRY_AFTER) - System.currentTimeMillis(), 0L);
reason = "Retry-After header with date value " + retryAfter;
} else {
delay = Long.parseLong(retryAfter.trim()) * 1000L; // is in seconds
reason = "Retry-After header with seconds value " + retryAfter;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Retry-After heuristic: prefer try-parse-as-long over contains(':').

RFC 7231 defines Retry-After as either a delta-seconds value (all digits) or an HTTP-date (contains :). The current heuristic works for all well-formed values, but the more robust and self-documenting approach is to try parsing as a long first and fall back to date parsing — consistent with the outer catch (IllegalArgumentException) which already covers NumberFormatException.

Suggested change
if (retryAfter.contains(":")) { // is date when to retry
delay = Math.max(
responseHeaders.getDateField(HttpHeader.RETRY_AFTER) - System.currentTimeMillis(), 0L);
reason = "Retry-After header with date value " + retryAfter;
} else {
delay = Long.parseLong(retryAfter.trim()) * 1000L; // is in seconds
reason = "Retry-After header with seconds value " + retryAfter;
}
try {
long seconds = Long.parseLong(retryAfter.trim());
delay = seconds * 1000L; // is in seconds
reason = "Retry-After header with seconds value " + retryAfter;
} catch (NumberFormatException nfe) {
// not a delta-seconds value, try parsing as HTTP-date
delay = Math.max(
responseHeaders.getDateField(HttpHeader.RETRY_AFTER) - System.currentTimeMillis(), 0L);
reason = "Retry-After header with date value " + retryAfter;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I kept the contains(":") check so it behaves the same as ApacheTransporter, which tells a date from seconds the same way. Happy to switch to try-parse-first if you'd prefer.

@github-actions

This comment has been minimized.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my earlier review — the resource leak is addressed, and the Retry-After heuristic I flagged is functionally correct.

✅ Addressed: InputStreamResponseListener not closed on exception retry — listener.close() added before continue (commit f3fadbc).

💡 Still open (non-blocking): Retry-After contains(":") heuristic — RFC 7231 guarantees all well-formed values are correctly classified (delta-seconds never contain a colon; HTTP-dates always do). The original suggestion to try parseLong first is stylistic; there's no correctness issue here. Leaving the thread open for the author to address or close at their discretion.

Previous findings resolved. LGTM.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on my earlier review — the single commit since my last LGTM (beb12a7) is a test-only change.

✅ testPut_RetriesAfterClosedConnection — removed the file-content assertion after a closed-connection retry sequence. The comment correctly explains the issue: when the server closes the connection immediately (setConnectionsToClose), the handler may have partially executed, potentially leaving the file in an indeterminate state. The assertion was therefore brittle and its removal is correct.

💡 Still open (non-blocking): The Retry-After contains(":") heuristic thread — as noted in my previous review, this is functionally correct per RFC 7231 and the remaining thread is purely stylistic. No action required.

LGTM — ready to merge.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement retry handling in Jetty Transporter

3 participants