Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GraalVM's native-image #280

Closed
vlsi opened this issue Jun 10, 2018 · 10 comments
Closed

Support GraalVM's native-image #280

vlsi opened this issue Jun 10, 2018 · 10 comments
Assignees
Labels
type: enhancement New feature or request
Milestone

Comments

@vlsi
Copy link

vlsi commented Jun 10, 2018

Steps to Reproduce

  1. build hello-world-kotlin-0.1-all.jar

  2. process it through native-image:

    mx native-image --class-path hello-world-kotlin-0.1-all.jar -H:ReflectionConfigurationFiles=reflection_m.json -H:Name=hw -H:Class=example.Application -H:+ReportUnsupportedElementsAtRuntime

Expected Behaviour

native-image should be able to compile micronaut app to native image.

Current GraalVM/SubstrateVM versions do not support "thread startup" from static initializers.
Caffeine uses ForkJoinPool by default for housekeeping purposes, so the following line breaks native-image generation:

ConversionService<?> SHARED = new DefaultConversionService();

The thing is DefaultConversionService registers default conversions, and it triggers Caffeine to create a task for ForkJoin, and it causes a thread to start:

Of course it would be great if SubstrateVM could recognize threads from static initializers, however it does not look like a short-term solution (see oracle/graal#432 )

I think it would be nice if Micronaut could move registration from static initializer to public static void main method (or something like that). Is it feasible?
Alternative solution could be @ben-manes to create "postpone housekeeping" API, so one could create "thread-free" static-initialized caches.

Just for the reference, Caffeine stack looks like follows:

	at com.github.benmanes.caffeine.cache.BoundedLocalCache.executor(BoundedLocalCache.java:244)
	at com.github.benmanes.caffeine.cache.BoundedLocalCache.scheduleDrainBuffers(BoundedLocalCache.java:1070)
	at com.github.benmanes.caffeine.cache.BoundedLocalCache.scheduleAfterWrite(BoundedLocalCache.java:1037)
	at com.github.benmanes.caffeine.cache.BoundedLocalCache.afterWrite(BoundedLocalCache.java:1007)
	at com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2096)
	at com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2012)
	at com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:113)
	at com.github.benmanes.caffeine.cache.LocalManualCache.get(LocalManualCache.java:54)

Actual Behaviour

RecomputeFieldValue.ArrayIndexScale automatic substitution failed. The automatic substitution registration was attempted because a call to sun.misc.Unsafe.arrayIndexScale(Class) was detected in the static initializer of io.micronaut.caffeine.cache.UnsafeRefArrayAccess. Add a RecomputeFieldValue.ArrayIndexScale manual substitution for io.micronaut.caffeine.cache.UnsafeRefArrayAccess.
    analysis:  13,422.23 ms
error: unsupported features in 2 methods
Detailed message:
Error: Detected a started Thread in the image heap. This is not supported. The object was reached from a static initializer. All static class initialization is done during native image construction, thus a static initializer cannot contain code that captures state dependent on the build machine. Write your own initialization methods and call them explicitly from your main entry point.
Trace: 	object java.util.concurrent.ForkJoinPool$WorkQueue
	object java.util.concurrent.ForkJoinPool$WorkQueue[]
	object java.util.concurrent.ForkJoinPool
	object io.micronaut.caffeine.cache.LocalCacheFactory$SSMS
	object io.micronaut.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache
	object io.micronaut.core.convert.DefaultConversionService
	method io.micronaut.context.BeanDefinitionDelegate.build(BeanResolutionContext, BeanContext, BeanDefinition)
Call path from entry point to io.micronaut.context.BeanDefinitionDelegate.build(BeanResolutionContext, BeanContext, BeanDefinition):
	at io.micronaut.context.BeanDefinitionDelegate.build(BeanDefinitionDelegate.java:185)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1192)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:1554)
	at io.micronaut.context.DefaultBeanContext.initializeContext(DefaultBeanContext.java:948)
	at io.micronaut.context.DefaultApplicationContext.initializeContext(DefaultApplicationContext.java:189)
	at io.micronaut.context.DefaultBeanContext.readAllBeanDefinitionClasses(DefaultBeanContext.java:1673)
	at io.micronaut.context.DefaultBeanContext.start(DefaultBeanContext.java:171)
	at io.micronaut.context.DefaultApplicationContext.start(DefaultApplicationContext.java:139)
	at io.micronaut.runtime.Micronaut.start(Micronaut.java:65)
	at example.Application.main(Application.kt:34)
	at com.oracle.svm.reflect.proxies.Proxy_1_Application_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:171)
	at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: Detected a started Thread in the image heap. This is not supported. The object was reached from a static initializer. All static class initialization is done during native image construction, thus a static initializer cannot contain code that captures state dependent on the build machine. Write your own initialization methods and call them explicitly from your main entry point.
Trace: 	object java.util.concurrent.ForkJoinPool$WorkQueue
	object java.util.concurrent.ForkJoinPool$WorkQueue[]
	object java.util.concurrent.ForkJoinPool
	object io.micronaut.caffeine.cache.LocalCacheFactory$SSMS
	object io.micronaut.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache
	object io.micronaut.core.convert.DefaultConversionService
	method io.micronaut.context.BeanDefinitionDelegate.build(BeanResolutionContext, BeanContext, BeanDefinition)
Call path from entry point to io.micronaut.context.BeanDefinitionDelegate.build(BeanResolutionContext, BeanContext, BeanDefinition):
	at io.micronaut.context.BeanDefinitionDelegate.build(BeanDefinitionDelegate.java:185)
	at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1192)
	at io.micronaut.context.DefaultBeanContext.createAndRegisterSingleton(DefaultBeanContext.java:1554)
	at io.micronaut.context.DefaultBeanContext.initializeContext(DefaultBeanContext.java:948)
	at io.micronaut.context.DefaultApplicationContext.initializeContext(DefaultApplicationContext.java:189)
	at io.micronaut.context.DefaultBeanContext.readAllBeanDefinitionClasses(DefaultBeanContext.java:1673)
	at io.micronaut.context.DefaultBeanContext.start(DefaultBeanContext.java:171)
	at io.micronaut.context.DefaultApplicationContext.start(DefaultApplicationContext.java:139)
	at io.micronaut.runtime.Micronaut.start(Micronaut.java:65)
	at example.Application.main(Application.kt:34)
	at com.oracle.svm.reflect.proxies.Proxy_1_Application_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:171)
	at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)

Environment Information

  • Operating System: macos
  • Micronaut Version:: io.micronaut:bom:1.0.0.M1
  • JDK Version: labsjdk1.8.0_172-jvmci-0.44 + SubstrateVM ef7aeea7d952602de7e66fb03df73d8dc3523d19

Example Application

https://github.com/micronaut-projects/micronaut-examples/tree/master/hello-world-kotlin

@graemerocher
Copy link
Contributor

Could you attach your rejection.json config?

@ben-manes
Copy link

For Caffeine, I'd recommend using a custom Executor. That usage could use a same-thread executor (Runnable::run) and probably not observe any issues. If there is a lifecycle state that can be listened or queried for, a more advanced cache usage (e.g. with a removal listener) might use a same-thread and switch to FJP afterwards. So I think the Caffeine.executor is probably the best place rather than introduce a new API.

@vlsi
Copy link
Author

vlsi commented Jun 10, 2018

@graemerocher , my reflection.json is empty at the moment: []

@ben-manes , I thought Executor was final in Caffeine, so there was no way to change Executor later.

Caffeine defaults to FJP. Do you mean the way to go is to override to same-thread all over the place?

@ben-manes
Copy link

Yeah, I'd override per-usage since Caffeine doesn't provide any global singletons, e.g. CacheManager, to orchestrate things. Its more common to use DI rather than statically configure the cache, e.g. to allow overriding the default maximumSize. Then injecting in a custom executor could be as simple as supplying an instance to @Provider methods for the instance construction.

@graemerocher graemerocher self-assigned this Jun 12, 2018
@graemerocher
Copy link
Contributor

@ben-manes Since ConversionService is required in lots of different places it is the one case where we need a shared statically initialized object. The majority of caches are built with DI. We will either remove usage of Caffeine for this case or apply the workaround you suggest.

@graemerocher
Copy link
Contributor

I have made some changes on the graalvm branch that get passed this error, but there are still other issues with the nativeimage tool. I have raised an issue here oracle/graal#470 with questions on how to resolved those.

If a solution is possible I will update this ticket.

@graemerocher graemerocher added the type: enhancement New feature or request label Jun 29, 2018
@graemerocher graemerocher changed the title [native-image] static DefaultConversionService() breaks GraalVM's native-image Support GraalVM's native-image Jun 29, 2018
@graemerocher graemerocher added this to the 1.0.0.RC1 milestone Sep 5, 2018
@graemerocher
Copy link
Contributor

Micronaut now works with Graal native image. See https://github.com/graemerocher/micronaut-graal-experiments/tree/master/hello-world-java for an example.

@limcheekin
Copy link

limcheekin commented Sep 10, 2018

thanks for creating the example. I follow the steps, but cannot found build/reflect.json? Do I need to install Micronaut before running the example?

@graemerocher
Copy link
Contributor

graemerocher commented Sep 10, 2018

Currently you need Micronaut built from master

@limcheekin
Copy link

alright. thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants