I have the following simplified example involving a plexus container:
package de.vogel612.depanalyzer;
import de.vogel612.depanalyzer.dependency.MavenResolutionTaskReal;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.artifact.repository.layout.FlatRepositoryLayout;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.RemoteRepositoryManager;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
public class MCVE {
private static final ServiceLocator serviceLocator = MavenRepositorySystemUtils.newServiceLocator()
.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class)
.addService(TransporterFactory.class, FileTransporterFactory.class)
.addService(TransporterFactory.class, HttpTransporterFactory.class);
private static final PriorityBlockingQueue<ResolutionTask> taskQueue = new PriorityBlockingQueue<>();
private static final ExecutorService consumer = Executors.newSingleThreadExector();
// actually initialized in main, since number of threads is configurable
private static ExecutorService workers = Executors.newFixedThreadPool(threads, runnable -> {
Thread result = new Thread(runnable, "WorkerQueue Thread");
result.setDaemon(true);
return result;
});
public static void main(String[] args) throws Exception {
PlexusContainer container = new DefaultPlexusContainer();
// bind eclipse aether RepositorySystem
container.addComponent(serviceLocator.getService(RepositorySystem.class), RepositorySystem.class, "default");
container.addComponent(serviceLocator.getService(RemoteRepositoryManager.class), RemoteRepositoryManager.class, "default");
container.addComponent(new DefaultRepositoryLayout(), ArtifactRepositoryLayout.class, "default");
container.addComponent(new FlatRepositoryLayout(), ArtifactRepositoryLayout.class, "flat");
taskQueue.put(container.lookup(MavenResolutionTask.class));
// ...
}
}
Do note the two last container.addComponent
lines. Now when I use the container in the following setup I've observed some very curious behaviour:
public class ResolutionTask extends CompletableFuture<List<SomeType>> implements Runnable {
// actually contains an override for run, which does error handling
}
public class MavenResolutionTask extends ResolutionTask {
@Requires
Maven mavenInstance;
@Override
public void run() {
// use the maven instance to compute some stuff:
complete(Collections.singletonList());
}
}
This code works just fine and performs as intended:
List<DependencyResult> results = new ArrayList<>();
while (!taskQueue.isEmpty()) {
ResolutionTask currentTask = taskQueue.poll();
workers.execute(currentTask);
results.addAll(currentTask.join());
}
results.forEach(System.out::println);
And as soon as this simplistic work-queue handler is moved off the main-thread as follows, I receive an exception:
consumer.execute(() -> {
List<DependencyResult> results = new ArrayList<>();
while (!taskQueue.isEmpty()) {
ResolutionTask currentTask = taskQueue.poll();
workers.execute(currentTask);
results.addAll(currentTask.join());
}
results.forEach(System.out::println);
});
Exception in thread "pool-1-thread-1" java.util.concurrent.CompletionException: java.lang.RuntimeException: Exception occurred during maven invocation
at java.util.concurrent.CompletableFuture.reportJoin(CompletableFuture.java:375)
at java.util.concurrent.CompletableFuture.join(CompletableFuture.java:1934)
at de.vogel612.depanalyzer.Main.lambda$main$1(Main.java:109)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException: Exception occurred during maven invocation
at de.vogel612.depanalyzer.dependency.MavenResolutionTask.runImpl(MavenResolutionTask.java:35)
at de.vogel612.depanalyzer.dependency.ResolutionTask.run(ResolutionTask.java:27)
... 3 more
Suppressed: org.apache.maven.InternalErrorException: Internal error: java.lang.NullPointerException
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:164)
at de.vogel612.depanalyzer.dependency.MavenResolutionTask.runImpl(MavenResolutionTask.java:32)
... 4 more
Caused by: java.lang.NullPointerException
at org.apache.maven.RepositoryUtils.getLayout(RepositoryUtils.java:217)
at org.apache.maven.RepositoryUtils.toRepo(RepositoryUtils.java:201)
at org.apache.maven.RepositoryUtils.toRepos(RepositoryUtils.java:191)
at org.apache.maven.project.DefaultProjectBuilder$InternalConfig.<init>(DefaultProjectBuilder.java:684)
at org.apache.maven.project.DefaultProjectBuilder.build(DefaultProjectBuilder.java:340)
at org.apache.maven.DefaultMaven.collectProjects(DefaultMaven.java:637)
at org.apache.maven.DefaultMaven.getProjectsForMavenReactor(DefaultMaven.java:586)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:229)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:152)
... 5 more
The "root cause" of this exception points to a place which isn't actually helpful. When maven tries to initialize a remote repository it accesses a Map<String, ArtifactRepositoryLayout>
, which should contain the entries {"default", new DefaultRepositoryLayout()},{"flat", new FlatRepositoryLayout()}
, but doesn't
This Map can be found in maven-compat's LegacyRepositorySystem, but it's not correctly initialized when the taskQueue is accessed in consumer
.
The code does produce the correct output when taking consumer
out of the equation ...
Why does this happen?