Skip to content

Commit d2bb1ce

Browse files
authored
Merge pull request #51272 from aloubyansky/retry-acquire-lock-errors
Re-try Maven resolver lock acquisition errors with a blocking task runner
2 parents 1ee41b9 + 18688d0 commit d2bb1ce

File tree

7 files changed

+235
-107
lines changed

7 files changed

+235
-107
lines changed

independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyResolver.java

Lines changed: 10 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,17 @@
77
import static io.quarkus.bootstrap.util.DependencyUtils.newDependencyBuilder;
88

99
import java.io.BufferedReader;
10-
import java.io.FileNotFoundException;
1110
import java.io.IOException;
1211
import java.io.UncheckedIOException;
1312
import java.nio.file.Files;
14-
import java.nio.file.NoSuchFileException;
1513
import java.util.ArrayDeque;
1614
import java.util.ArrayList;
1715
import java.util.Collection;
1816
import java.util.Collections;
1917
import java.util.HashMap;
20-
import java.util.HashSet;
2118
import java.util.List;
2219
import java.util.Map;
2320
import java.util.Properties;
24-
import java.util.Set;
2521
import java.util.concurrent.ConcurrentHashMap;
2622
import java.util.concurrent.ConcurrentLinkedDeque;
2723
import java.util.function.BiConsumer;
@@ -294,8 +290,14 @@ private void injectDeploymentDeps() {
294290

295291
private Collection<AppDep> collectDeploymentDeps() {
296292
final ConcurrentLinkedDeque<AppDep> injectQueue = new ConcurrentLinkedDeque<>();
297-
var taskRunner = deploymentInjectionPoints.size() == 1 ? ModelResolutionTaskRunner.getBlockingTaskRunner()
298-
: getTaskRunner();
293+
final ModelResolutionTaskRunner taskRunner;
294+
if (deploymentInjectionPoints.size() == 1 || BLOCKING_TASK_RUNNER) {
295+
taskRunner = ModelResolutionTaskRunner.getBlockingTaskRunner();
296+
} else {
297+
// We've been running into Maven resolver failures to acquire a lock to a local fail when resolving dependencies lately.
298+
// This error handler will catch those errors and will re-try the corresponding tasks with the blocking task runner.
299+
taskRunner = ModelResolutionTaskRunner.getNonBlockingTaskRunner(new RetryLockAcquisitionErrorHandler());
300+
}
299301
for (AppDep extDep : deploymentInjectionPoints) {
300302
extDep.scheduleCollectDeploymentDeps(taskRunner, injectQueue);
301303
}
@@ -821,72 +823,20 @@ private static Properties readExtensionProperties(PathVisit visit) {
821823
private DependencyNode collectDependencies(Artifact artifact, Collection<Exclusion> exclusions,
822824
List<RemoteRepository> repos) {
823825
final CollectRequest collectRequest = getCollectRequest(artifact, exclusions, repos);
824-
DependencyNode root = null;
826+
final DependencyNode root;
825827
try {
826828
root = resolver.getSystem()
827829
.collectDependencies(resolver.getSession(), collectRequest)
828830
.getRoot();
829831
} catch (DependencyCollectionException e) {
830-
// It could happen, especially in Maven 3.8, that multiple threads could end up writing/reading
831-
// the same temporary files while resolving the same artifact. Once one of the threads completes
832-
// resolving the artifact, the temporary file will be renamed to the target artifact file
833-
// and the other thread will fail with one of the file-not-found exceptions.
834-
// In this case, we simply re-try the collect request, which should now pick up the already resolved artifact.
835-
String missingFile = getMissingFileOrNull(e);
836-
if (missingFile != null) {
837-
Set<String> missingFiles = new HashSet<>();
838-
while (missingFile != null) {
839-
if (missingFiles.add(missingFile)) {
840-
log.debugf("Re-trying the collect request for %s due to missing %s", artifact, missingFile);
841-
try {
842-
root = resolver.getSystem()
843-
.collectDependencies(resolver.getSession(), collectRequest)
844-
.getRoot();
845-
break;
846-
} catch (DependencyCollectionException dce) {
847-
missingFile = getMissingFileOrNull(dce);
848-
}
849-
} else {
850-
// if it's the second time it's missing, we give up
851-
throw wrapInDeploymentInjectionException(artifact, e);
852-
}
853-
}
854-
}
855-
if (root == null) {
856-
throw wrapInDeploymentInjectionException(artifact, e);
857-
}
832+
throw new DeploymentInjectionException("Failed to collect dependencies for " + artifact, e);
858833
}
859834
if (root.getChildren().size() != 1) {
860835
throw new DeploymentInjectionException("Only one child expected but got " + root.getChildren());
861836
}
862837
return root.getChildren().get(0);
863838
}
864839

865-
private static DeploymentInjectionException wrapInDeploymentInjectionException(Artifact artifact, Exception e) {
866-
return new DeploymentInjectionException("Failed to collect dependencies for " + artifact, e);
867-
}
868-
869-
/**
870-
* Checks whether the cause of this exception a kind of no-such-file exception and returns the file that was missing.
871-
*
872-
* @param dce top level exception
873-
* @return missing file that was the cause or null, if the cause was different
874-
*/
875-
private static String getMissingFileOrNull(DependencyCollectionException dce) {
876-
Throwable t = dce.getCause();
877-
while (t != null) {
878-
var cause = t.getCause();
879-
// It looks like in Maven 3.9 it's NoSuchFileException, while in Maven 3.8 it's FileNotFoundException
880-
if (cause instanceof NoSuchFileException e) {
881-
return e.getFile();
882-
} else if (cause instanceof FileNotFoundException) {
883-
return cause.getMessage();
884-
}
885-
t = cause;
886-
}
887-
return null;
888-
}
889-
890840
private CollectRequest getCollectRequest(Artifact artifact, Collection<Exclusion> exclusions,
891841
List<RemoteRepository> repos) {
892842
final ArtifactDescriptorResult descr;

independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BlockingModelResolutionTaskRunner.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,36 @@
88
class BlockingModelResolutionTaskRunner implements ModelResolutionTaskRunner {
99

1010
static BlockingModelResolutionTaskRunner getInstance() {
11-
return new BlockingModelResolutionTaskRunner();
11+
return new BlockingModelResolutionTaskRunner(null);
12+
}
13+
14+
static BlockingModelResolutionTaskRunner getInstance(ModelResolutionTaskErrorHandler errorHandler) {
15+
return new BlockingModelResolutionTaskRunner(errorHandler);
16+
}
17+
18+
private final ModelResolutionTaskErrorHandler errorHandler;
19+
20+
BlockingModelResolutionTaskRunner(ModelResolutionTaskErrorHandler errorHandler) {
21+
this.errorHandler = errorHandler;
1222
}
1323

1424
@Override
1525
public void run(ModelResolutionTask task) {
16-
task.run();
26+
if (errorHandler == null) {
27+
task.run();
28+
return;
29+
}
30+
try {
31+
task.run();
32+
} catch (Exception e) {
33+
errorHandler.handleError(task, e);
34+
}
1735
}
1836

1937
@Override
2038
public void waitForCompletion() {
39+
if (errorHandler != null) {
40+
errorHandler.allTasksFinished();
41+
}
2142
}
2243
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.quarkus.bootstrap.resolver.maven;
2+
3+
import java.util.Collection;
4+
import java.util.concurrent.ConcurrentLinkedDeque;
5+
6+
import org.jboss.logging.Logger;
7+
8+
/**
9+
* Error handler that collects all the task execution errors and throws a single one reporting all the captured
10+
* failures once all the tasks have finished.
11+
*/
12+
public class FailAtCompletionErrorHandler implements ModelResolutionTaskErrorHandler {
13+
14+
private static final Logger log = Logger.getLogger(FailAtCompletionErrorHandler.class);
15+
16+
/**
17+
* Errors caught while running tasks
18+
*/
19+
private final Collection<Exception> errors = new ConcurrentLinkedDeque<>();
20+
21+
@Override
22+
public void handleError(ModelResolutionTask task, Exception error) {
23+
errors.add(error);
24+
}
25+
26+
@Override
27+
public void allTasksFinished() {
28+
if (!errors.isEmpty()) {
29+
throwFailureReport(errors);
30+
}
31+
}
32+
33+
protected boolean isEmpty() {
34+
return errors.isEmpty();
35+
}
36+
37+
private static void throwFailureReport(Collection<Exception> errors) {
38+
var sb = new StringBuilder(
39+
"The following errors were encountered while processing Quarkus application dependencies:");
40+
log.error(sb);
41+
var i = 1;
42+
for (var error : errors) {
43+
var prefix = i++ + ")";
44+
log.error(prefix, error);
45+
sb.append(System.lineSeparator()).append(prefix).append(" ").append(error.getLocalizedMessage());
46+
for (var e : error.getStackTrace()) {
47+
sb.append(System.lineSeparator());
48+
for (int j = 0; j < prefix.length(); ++j) {
49+
sb.append(" ");
50+
}
51+
sb.append("at ").append(e);
52+
if (e.getClassName().contains("io.quarkus")) {
53+
sb.append(System.lineSeparator());
54+
for (int j = 0; j < prefix.length(); ++j) {
55+
sb.append(" ");
56+
}
57+
sb.append("...");
58+
break;
59+
}
60+
}
61+
}
62+
throw new RuntimeException(sb.toString());
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.quarkus.bootstrap.resolver.maven;
2+
3+
public interface ModelResolutionTaskErrorHandler {
4+
5+
void handleError(ModelResolutionTask task, Exception error);
6+
7+
default void allTasksFinished() {
8+
}
9+
}

independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ModelResolutionTaskRunner.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,45 @@
66
public interface ModelResolutionTaskRunner {
77

88
/**
9-
* Returns an instance of a non-blocking task runner.
9+
* Returns an instance of a non-blocking task runner with an error handler
10+
* that collects all task execution errors and throws a single error when all the tasks
11+
* have finished.
1012
*
1113
* @return an instance of a non-blocking task runner
1214
*/
1315
static ModelResolutionTaskRunner getNonBlockingTaskRunner() {
14-
return new NonBlockingModelResolutionTaskRunner();
16+
return getNonBlockingTaskRunner(new FailAtCompletionErrorHandler());
1517
}
1618

1719
/**
18-
* Returns an instance of a blocking task runner.
20+
* Returns an instance of a non-blocking task runner with a custom error handler.
21+
*
22+
* @param errorHandler error handler
23+
* @return an instance of non-blocking task runner
24+
*/
25+
static ModelResolutionTaskRunner getNonBlockingTaskRunner(ModelResolutionTaskErrorHandler errorHandler) {
26+
return new NonBlockingModelResolutionTaskRunner(errorHandler);
27+
}
28+
29+
/**
30+
* Returns an instance of a blocking task runner with no error handler.
31+
* Exceptions thrown from tasks will be immediately propagated to the caller.
1932
*
2033
* @return an instance of a blocking task runner
2134
*/
2235
static ModelResolutionTaskRunner getBlockingTaskRunner() {
2336
return BlockingModelResolutionTaskRunner.getInstance();
2437
}
2538

39+
/**
40+
* Returns an instance of a blocking task runner with a custom error handler.
41+
*
42+
* @return an instance of a blocking task runner
43+
*/
44+
static ModelResolutionTaskRunner getBlockingTaskRunner(ModelResolutionTaskErrorHandler errorHandler) {
45+
return BlockingModelResolutionTaskRunner.getInstance(errorHandler);
46+
}
47+
2648
/**
2749
* Instructs a runner to run the passed in task.
2850
* <p>
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
package io.quarkus.bootstrap.resolver.maven;
22

3-
import java.util.Collection;
3+
import java.util.Objects;
44
import java.util.concurrent.CompletableFuture;
5-
import java.util.concurrent.ConcurrentLinkedDeque;
65
import java.util.concurrent.Phaser;
76

8-
import org.jboss.logging.Logger;
9-
107
/**
118
* Non-blocking task runner implementation
129
*/
1310
class NonBlockingModelResolutionTaskRunner implements ModelResolutionTaskRunner {
1411

15-
private static final Logger log = Logger.getLogger(NonBlockingModelResolutionTaskRunner.class);
16-
1712
private final Phaser phaser = new Phaser(1);
1813

19-
/**
20-
* Errors caught while running tasks
21-
*/
22-
private final Collection<Exception> errors = new ConcurrentLinkedDeque<>();
14+
private final ModelResolutionTaskErrorHandler errorHandler;
15+
16+
NonBlockingModelResolutionTaskRunner(ModelResolutionTaskErrorHandler errorHandler) {
17+
this.errorHandler = Objects.requireNonNull(errorHandler, "errorHandler cannot be null");
18+
}
2319

2420
/**
2521
* Runs a model resolution task asynchronously. This method may return before the task has completed.
@@ -33,7 +29,7 @@ public void run(ModelResolutionTask task) {
3329
try {
3430
task.run();
3531
} catch (Exception e) {
36-
errors.add(e);
32+
errorHandler.handleError(task, e);
3733
} finally {
3834
phaser.arriveAndDeregister();
3935
}
@@ -49,36 +45,6 @@ public void run(ModelResolutionTask task) {
4945
@Override
5046
public void waitForCompletion() {
5147
phaser.arriveAndAwaitAdvance();
52-
assertNoErrors();
53-
}
54-
55-
private void assertNoErrors() {
56-
if (!errors.isEmpty()) {
57-
var sb = new StringBuilder(
58-
"The following errors were encountered while processing Quarkus application dependencies:");
59-
log.error(sb);
60-
var i = 1;
61-
for (var error : errors) {
62-
var prefix = i++ + ")";
63-
log.error(prefix, error);
64-
sb.append(System.lineSeparator()).append(prefix).append(" ").append(error.getLocalizedMessage());
65-
for (var e : error.getStackTrace()) {
66-
sb.append(System.lineSeparator());
67-
for (int j = 0; j < prefix.length(); ++j) {
68-
sb.append(" ");
69-
}
70-
sb.append("at ").append(e);
71-
if (e.getClassName().contains("io.quarkus")) {
72-
sb.append(System.lineSeparator());
73-
for (int j = 0; j < prefix.length(); ++j) {
74-
sb.append(" ");
75-
}
76-
sb.append("...");
77-
break;
78-
}
79-
}
80-
}
81-
throw new RuntimeException(sb.toString());
82-
}
48+
errorHandler.allTasksFinished();
8349
}
8450
}

0 commit comments

Comments
 (0)