Skip to content

Commit eac858b

Browse files
Copilotvharseko
andauthored
Add download retry mechanism with configurable attempts and timeout (#179)
Agent-Logs-Url: https://github.com/OpenIdentityPlatform/commons/sessions/fabc1d64-f9e4-46ed-bbc9-c924cc6a7ecd Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: vharseko <6818498+vharseko@users.noreply.github.com>
1 parent de25dc6 commit eac858b

3 files changed

Lines changed: 255 additions & 16 deletions

File tree

maven-external-dependency-plugin/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,25 @@
22
[![Build Status](https://travis-ci.org/openam-org-ru/maven-external-dependency-plugin.svg)](https://travis-ci.org/openam-org-ru/maven-external-dependency-plugin)
33

44
Forked from https://code.google.com/archive/p/maven-external-dependency-plugin/
5+
6+
## Download retry configuration
7+
8+
To make external artifact downloads resilient to transient network errors
9+
(e.g. `java.net.ConnectException: Connection timed out`), the
10+
`resolve-external` goal retries failed downloads.
11+
12+
Mojo-level configuration parameters (apply to every `<artifactItem>` unless
13+
overridden on the artifact itself):
14+
15+
| Parameter | Default | Description |
16+
|-------------------------|---------|--------------------------------------------------------------|
17+
| `downloadRetryAttempts` | `5` | Number of attempts before giving up on a single download. |
18+
| `downloadTimeout` | `10000` | Per-attempt connection/transfer timeout in milliseconds. |
19+
| `downloadRetryDelay` | `2000` | Delay in milliseconds between retry attempts. |
20+
21+
Per-artifact overrides (defined inside `<artifactItem>`): `timeout`,
22+
`retryAttempts`, `retryDelay`. When set, they take precedence over the
23+
Mojo-level defaults.
24+
25+
Authorization failures are not retried; the build fails immediately on
26+
`AuthorizationException`.

maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ArtifactItem.java

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ public class ArtifactItem
9090
*/
9191
private Integer timeout;
9292

93+
/**
94+
* Per-artifact override for the number of attempts to download this
95+
* artifact in case of transient network failures. When unset, the
96+
* Mojo-level {@code downloadRetryAttempts} parameter (default 5) is used.
97+
*
98+
* @parameter
99+
*/
100+
private Integer retryAttempts;
101+
102+
/**
103+
* Per-artifact override for the delay in millis between download retry
104+
* attempts. When unset, the Mojo-level {@code downloadRetryDelay}
105+
* parameter (default 2000 ms) is used.
106+
*
107+
* @parameter
108+
*/
109+
private Integer retryDelay;
110+
93111
/**
94112
* Packaging type of the artifact to be installed.
95113
*
@@ -387,7 +405,17 @@ public final void setDownloadUrl(final String downloadUrl)
387405
*/
388406
public final Integer getTimeout()
389407
{
390-
return (timeout==null||timeout<=0)?5000:timeout;
408+
return (timeout==null||timeout<=0)?10000:timeout;
409+
}
410+
411+
/**
412+
* @return Raw timeout value as configured (may be null) so callers can
413+
* distinguish an explicitly set per-artifact timeout from the
414+
* default fallback returned by {@link #getTimeout()}.
415+
*/
416+
public final Integer getTimeoutRaw()
417+
{
418+
return timeout;
391419
}
392420

393421
/**
@@ -399,6 +427,41 @@ public final void setTimeout(final Integer timeout)
399427
this.timeout = timeout;
400428
}
401429

430+
/**
431+
* @return Raw retry attempts value as configured (may be null).
432+
*/
433+
public final Integer getRetryAttempts()
434+
{
435+
return retryAttempts;
436+
}
437+
438+
/**
439+
* @param retryAttempts
440+
* Number of attempts to download the artifact in case of
441+
* transient network failures.
442+
*/
443+
public final void setRetryAttempts(final Integer retryAttempts)
444+
{
445+
this.retryAttempts = retryAttempts;
446+
}
447+
448+
/**
449+
* @return Raw retry delay value (in millis) as configured (may be null).
450+
*/
451+
public final Integer getRetryDelay()
452+
{
453+
return retryDelay;
454+
}
455+
456+
/**
457+
* @param retryDelay
458+
* Delay in millis between download retry attempts.
459+
*/
460+
public final void setRetryDelay(final Integer retryDelay)
461+
{
462+
this.retryDelay = retryDelay;
463+
}
464+
402465
/**
403466
* @return Packaging.
404467
*/

maven-external-dependency-plugin/maven-external-dependency-plugin/src/main/java/com/savage7/maven/plugin/dependency/ResolveExternalDependencyMojo.java

Lines changed: 169 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,31 @@ public class ResolveExternalDependencyMojo extends
9696
* @readonly
9797
*/
9898
private MavenSettingsBuilder mavenSettingsBuilder;
99+
100+
/**
101+
* Default number of attempts to download an external artifact in case
102+
* of transient network failures (e.g. connection timeouts). Used when
103+
* an {@code <artifactItem>} does not define its own {@code retryAttempts}.
104+
*
105+
* @parameter default-value="5"
106+
*/
107+
private Integer downloadRetryAttempts;
108+
109+
/**
110+
* Default timeout in milliseconds for artifact download attempts. Used
111+
* when an {@code <artifactItem>} does not define its own {@code timeout}.
112+
*
113+
* @parameter default-value="10000"
114+
*/
115+
private Integer downloadTimeout;
116+
117+
/**
118+
* Default delay in milliseconds between download retry attempts. Used
119+
* when an {@code <artifactItem>} does not define its own {@code retryDelay}.
120+
*
121+
* @parameter default-value="2000"
122+
*/
123+
private Integer downloadRetryDelay;
99124

100125
public void execute() throws MojoExecutionException, MojoFailureException
101126
{
@@ -183,14 +208,6 @@ public void execute() throws MojoExecutionException, MojoFailureException
183208
URL downloadUrl = new URL(artifactItem.getDownloadUrl());
184209
String endPointUrl = downloadUrl.getProtocol() + "://"+ downloadUrl.getAuthority();
185210
Repository repository = new Repository("additonal-configs", endPointUrl);
186-
Wagon wagon = wagonManager.getWagon(downloadUrl.getProtocol());
187-
if (getLog().isDebugEnabled())
188-
{
189-
Debug debug = new Debug();
190-
wagon.addSessionListener(debug);
191-
wagon.addTransferListener(debug);
192-
}
193-
wagon.setTimeout(artifactItem.getTimeout());
194211
Settings settings = mavenSettingsBuilder.buildSettings();
195212
ProxyInfo proxyInfo = null;
196213
if (settings != null&& settings.getActiveProxy() != null)
@@ -204,13 +221,93 @@ public void execute() throws MojoExecutionException, MojoFailureException
204221
proxyInfo.setUserName(settingsProxy.getUsername());
205222
proxyInfo.setPassword(settingsProxy.getPassword());
206223
}
207-
208-
if (proxyInfo != null)
209-
wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()),proxyInfo);
210-
else
211-
wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()));
212-
213-
wagon.get(downloadUrl.getPath().substring(1), tempDownloadFile);
224+
225+
// resolve effective retry / timeout settings:
226+
// per-artifact value (if set) wins over the
227+
// Mojo-level default.
228+
int effectiveAttempts = resolveRetryAttempts(artifactItem);
229+
int effectiveTimeout = resolveTimeout(artifactItem);
230+
long effectiveRetryDelay = resolveRetryDelay(artifactItem);
231+
232+
Exception lastFailure = null;
233+
for (int attempt = 1; attempt <= effectiveAttempts; attempt++)
234+
{
235+
Wagon wagon = wagonManager.getWagon(downloadUrl.getProtocol());
236+
if (getLog().isDebugEnabled())
237+
{
238+
Debug debug = new Debug();
239+
wagon.addSessionListener(debug);
240+
wagon.addTransferListener(debug);
241+
}
242+
wagon.setTimeout(effectiveTimeout);
243+
try
244+
{
245+
if (proxyInfo != null)
246+
wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()),proxyInfo);
247+
else
248+
wagon.connect(repository, wagonManager.getAuthenticationInfo(repository.getId()));
249+
250+
wagon.get(downloadUrl.getPath().substring(1), tempDownloadFile);
251+
// success: stop retrying
252+
lastFailure = null;
253+
break;
254+
}
255+
catch (org.apache.maven.wagon.authorization.AuthorizationException ae)
256+
{
257+
// authorization issues are not transient, fail fast
258+
throw ae;
259+
}
260+
catch (Exception ex)
261+
{
262+
lastFailure = ex;
263+
getLog().warn(
264+
"download attempt " + attempt + "/" + effectiveAttempts
265+
+ " failed for URL: " + artifactItem.getDownloadUrl()
266+
+ " - " + ex.getClass().getName() + ": " + ex.getMessage());
267+
268+
// discard partial download before retrying
269+
if (tempDownloadFile.exists() && !tempDownloadFile.delete())
270+
{
271+
getLog().debug("could not delete partial temp file: "
272+
+ tempDownloadFile.getAbsolutePath());
273+
}
274+
275+
if (attempt < effectiveAttempts && effectiveRetryDelay > 0)
276+
{
277+
try
278+
{
279+
Thread.sleep(effectiveRetryDelay);
280+
}
281+
catch (InterruptedException ie)
282+
{
283+
Thread.currentThread().interrupt();
284+
throw new MojoExecutionException(
285+
"Interrupted while waiting to retry download of "
286+
+ artifactItem.getDownloadUrl(), ie);
287+
}
288+
}
289+
}
290+
finally
291+
{
292+
try
293+
{
294+
wagon.disconnect();
295+
}
296+
catch (Exception ignored)
297+
{
298+
getLog().debug("error while disconnecting wagon: "
299+
+ ignored.getMessage());
300+
}
301+
}
302+
}
303+
304+
if (lastFailure != null)
305+
{
306+
throw new MojoExecutionException(
307+
"Failed to download artifact " + artifactItem.getDownloadUrl()
308+
+ " after " + effectiveAttempts + " attempt(s)",
309+
lastFailure);
310+
}
214311
}else {
215312
FileUtils.copyFile(new File(artifactItem.getDownloadUrl()), tempDownloadFile);
216313
}
@@ -440,4 +537,61 @@ protected boolean resolveArtifactItem(Artifact artifact)
440537

441538
return artifactResolved;
442539
}
540+
541+
/**
542+
* Resolve effective number of download attempts for the given artifact.
543+
* Per-artifact value (if set and positive) takes precedence over the
544+
* Mojo-level {@code downloadRetryAttempts} parameter. Falls back to 5.
545+
*/
546+
private int resolveRetryAttempts(ArtifactItem artifactItem)
547+
{
548+
Integer perArtifact = artifactItem.getRetryAttempts();
549+
if (perArtifact != null && perArtifact > 0)
550+
{
551+
return perArtifact;
552+
}
553+
if (downloadRetryAttempts != null && downloadRetryAttempts > 0)
554+
{
555+
return downloadRetryAttempts;
556+
}
557+
return 5;
558+
}
559+
560+
/**
561+
* Resolve effective download timeout (in millis) for the given artifact.
562+
* Per-artifact value (if explicitly set and positive) takes precedence
563+
* over the Mojo-level {@code downloadTimeout} parameter.
564+
*/
565+
private int resolveTimeout(ArtifactItem artifactItem)
566+
{
567+
Integer perArtifact = artifactItem.getTimeoutRaw();
568+
if (perArtifact != null && perArtifact > 0)
569+
{
570+
return perArtifact;
571+
}
572+
if (downloadTimeout != null && downloadTimeout > 0)
573+
{
574+
return downloadTimeout;
575+
}
576+
return 10000;
577+
}
578+
579+
/**
580+
* Resolve effective delay (in millis) between retry attempts for the
581+
* given artifact. Per-artifact value (if set and non-negative) takes
582+
* precedence over the Mojo-level {@code downloadRetryDelay} parameter.
583+
*/
584+
private long resolveRetryDelay(ArtifactItem artifactItem)
585+
{
586+
Integer perArtifact = artifactItem.getRetryDelay();
587+
if (perArtifact != null && perArtifact >= 0)
588+
{
589+
return perArtifact;
590+
}
591+
if (downloadRetryDelay != null && downloadRetryDelay >= 0)
592+
{
593+
return downloadRetryDelay;
594+
}
595+
return 2000L;
596+
}
443597
}

0 commit comments

Comments
 (0)