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?