quarkus: caffeine error while building native image: detected the ForkJoinPool.commonPool() in the image heap

Describe the bug

Error: Detected the ForkJoinPool.commonPool() in the image heap. The common pool must be created at run time because the parallelism depends on the number of cores available at run time. Therefore the common pool used during image generation must not be reachable, e.g., via a static field that caches a copy of the common pool. The object was probably created by a class initializer and is reachable from a static field. By default, all class initialization is done during native image building.You can manually delay class initialization to image run time by using the option -H:ClassInitialization=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.
Detailed message:
Trace: 	object com.github.benmanes.caffeine.cache.SSLMS
	object com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache
	object io.quarkus.it.caffeine.Application_ClientProxy
	object io.quarkus.it.caffeine.Application_Bean
	object java.lang.Object[]
	object java.util.ArrayList
	object io.quarkus.arc.ArcContainerImpl
	object java.util.concurrent.atomic.AtomicReference
	method io.quarkus.arc.Arc.shutdown()
Call path from entry point to io.quarkus.arc.Arc.shutdown(): 
	at io.quarkus.arc.Arc.shutdown(Arc.java:34)
	at io.quarkus.arc.runtime.ArcRecorder$1.run(ArcRecorder.java:37)
	at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:612)
	at org.xnio.nio.WorkerThread.run(WorkerThread.java:479)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:460)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)
	at com.oracle.svm.core.code.IsolateEnterStub.PosixJavaThreads_pthreadStartRoutine_e1f4a8c0039f8337338252cd8734f63a79b5e3df(generated:0)

To Reproduce

  1. checkout https://github.com/quarkusio/quarkus/pull/3301
  2. run caffeine’s integration-test

Environment (please complete the following information):

  • Output of java -version: 1.8
  • GraalVM version (if different from Java): 19.1.1

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

So I did try to create a substitution like:

@TargetClass(Caffeine.class)
final class Target_com_github_benmanes_caffeine_cache_Caffeine {
    @Alias
    @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
    Executor executor = new Executor() {
        @Override
        public void execute(Runnable task) {
            Executors.newSingleThreadExecutor().execute(task);
        }
    };

    @Substitute
    Executor getExecutor() {
        return executor;
    }
}

But this fails with an NPE:

Fatal error: java.lang.NullPointerException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
	at java.util.concurrent.ForkJoinTask.get(ForkJoinTask.java:1005)
	at com.oracle.svm.hosted.NativeImageGenerator.run(NativeImageGenerator.java:457)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:308)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:446)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:112)
Caused by: java.lang.NullPointerException
	at jdk.vm.ci.hotspot.HotSpotConstantReflectionProvider.readFieldValue(HotSpotConstantReflectionProvider.java:170)
	at com.oracle.svm.core.meta.ReadableJavaField.readFieldValue(ReadableJavaField.java:37)
	at com.oracle.svm.hosted.substitute.ComputedValueField.<init>(ComputedValueField.java:109)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.fieldValueRecomputation(AnnotationSubstitutionProcessor.java:745)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleFieldInAliasClass(AnnotationSubstitutionProcessor.java:404)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleAliasClass(AnnotationSubstitutionProcessor.java:304)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleClass(AnnotationSubstitutionProcessor.java:274)
	at com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.init(AnnotationSubstitutionProcessor.java:230)
	at com.oracle.svm.hosted.NativeImageGenerator.createDeclarativeSubstitutionProcessor(NativeImageGenerator.java:863)
	at com.oracle.svm.hosted.NativeImageGenerator.setupNativeImage(NativeImageGenerator.java:817)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:520)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:440)
	at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

The build suceed if the access to the commonPoll is wrapped ad construction time like:

public class Application {
    private final Cache<String, String> cache = Caffeine.newBuilder()
            .executor(task -> ForkJoinPool.commonPool().execute(task))
            .build();
}

@lburgazzoli I’ve just accross the same exception (see here) and I figured out what causes it. You get this exception when you instantiate something for which there’s a substitution.

In my case, I had something like this:

@Alias
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
private transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache = new ConcurrentHashMap<>();

The fix was to use Kind.NewInstance and then point to the declaring class, e.g.

@Alias
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ConcurrentHashMap.class)
private transient ConcurrentMap<Integer, ZoneOffsetTransition[]> lastRulesCache;

Note that I don’t know if this would have worked in your case, but thought I’d post it here for posterity, in case anyone faces a similar issue.