Incomplete plexus container injection when moving items in a separate thread

144 views Asked by At

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?

0

There are 0 answers