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

Improve GraalVM native support #22968

Closed
aclement opened this issue May 13, 2019 · 11 comments
Closed

Improve GraalVM native support #22968

aclement opened this issue May 13, 2019 · 11 comments

Comments

@aclement
Copy link
Contributor

@aclement aclement commented May 13, 2019

native-image is the command that compiles an application (in our case a Spring application) into a native executable. Configuration of the command can be done via command line options (passed directly or via static side files) or by building a Feature. In some cases command line options is fine but a Feature gives maximum flexibility as it is code that participates in the native-image execution process and can make dynamic decisions based on the proposed content of the executable.

There is a prototype of Spring Boot Feature in this project: https://github.com/spring-projects-experimental/spring-graalvm-native

The prototype is crude and currently uses a lot of fixed information which needs computing but the project proves that once that information is collected (and if we workaround that remaining Graal issue), native-image is happy to build Spring projects.

There are four kinds of data that are passed by the Feature as the native-image command executes:

  • reflection. We need to determine which types are going to be reflected on as the application runs, and if we tell the compiler it will generate metadata such that reflection will succeed at runtime.
  • resources. We need to determine which resources are going to be accessed as the application runs. If we tell the compiler about them they will be added to the executable image and available at runtime. This includes obvious files like spring.factories spring.components but also if the system is going to load the bytecode to run asm over it to find something, those class files also need to be available as resources.
  • proxies. We need to determine what proxies will be generated as the system runs. By telling the compiler about them it will generate them at image build time and they will be available at runtime when requested.
  • delayed initialization. The compiler will try to do as much initialization up front for faster execution later. This means executing class initializers as the image is built. This works great if building a data table for later lookup, but it does not work in all cases. For example if the code was creating a DirectByteBuffer. We need to compute any types that need to be delayed for runtime initialization and tell the compiler about them.

Some of this data can be computed easily - some types are already annotated (or meta-annotated) with something that indicates we'll need to add them to the reflection/resource lists. (@Configuration). The current prototype Feature doesn't do much computation (yet) but you can see the kind of data it is passing (and so needs computing) for all these four kinds of data here: https://github.com/aclement/spring-boot-graal-feature/tree/master/src/main/resources
(That is not the minimal set of data, it is just a set that works!)

JDK Proxy usage is allowed in applications being compiled but not dynamic class definition via CGLIB. To avoid the need to do that you can use the new proxyBeanMethods=false option. You can see @SpringBootApplication(proxyBeanMethods = false) in the sample demo app mentioned above. There are other ways we can handle this (annotation processor that generates the proxy at build time).

The sample project above is using the spring-content-indexer maven plugin to create a spring.components file for some processing. Not entirely clear this is necessary yet.

There is a challenge in drawing the line between framework and boot. A basic version of a feature for framework might scan for spring.factories and simply add any listed classes to the reflect/resource lists. The problem is whether that goes far enough to make something work. For example if spring.factories is being used to specify boots EnableAutoConfiguration then just adding the referred auto-configuration classes isn't enough. If those configuration classes use a ConditionalOnClass check, the types referred to in those conditions also need adding in order for the configuration to behave and framework has no knowledge of that need (only boot does).
Going further, and that is what the sample feature above does (as an experiment and to keep the size of the manually maintained json files): What if the ConditionalOnClass check was going to fail because at image build time we know the classpath and it isn't there? There is no need to add the auto configuration to the system because it will fail immediately. You can tweak resource files as the image is built (remove unnecessary entries in spring.factories for example). This would reduce image size and make the compiled result even faster. (As an example, boot 2.2 lists ~120 autoconfigurations in spring.factories, 100 of them are immediately going to fail ConditionalOnClass checks). Can both boot and framework have a feature? Sure, but how would you handle this case above when there are two?
Maybe keep it dumb initially and measure the impact (framework feature simply adds types referred to in spring.factories where the boot feature takes another pass and adds the deeper set of types, no smart exclusion).

Dependencies are likely to add their own features over time. For example the sample project above includes its own configuration for netty but netty now ships their own that we should simply rely on.

Also need to decide how to recommend users run native-image. It can be run as part of a maven build but you probably want it attached to a separate target because it takes a while (you probably wouldn't want to run it all the time). The sample project above unpacks the boot far jar before running it, that may not be necessary with a more sophisticated feature.

Having said all the above... taking a step back there is an alternative to a feature if we wish to go down the compiler plugin route. Annotation processors can produce the more static kind of data (the json flat files) that native-image can then pickup. Possibly slightly more advanced than your standard annotation processor as may need to dig deeply through some types to track down some things that need to be included in those files. In this situation we'd build spring itself with these processors and include the json files in each built artifact. The files would look a bit like those I linked above but be split up by jar that introduces those entries. The users application would also need to be built with the processor to produce the side files. What all this wouldn't allow is the more dynamic behaviour (excluding certain things as the image is built because you know the complete classpath for the system that is going to run).

@guanchao-yang
Copy link

@guanchao-yang guanchao-yang commented May 14, 2019

Spring Framework can work in GraalVM mode will be excited. And Which spring framework release version do we plan to support GraalVM with native image? 5.X Or ? Thanks

@sdeleuze
Copy link
Contributor

@sdeleuze sdeleuze commented May 14, 2019

@guanchao-yang Thanks for your interest but Spring Framework bug tracker is not expected to be used for asking question about the roadmap, this issue is in the 5.x backlog for now, we will share more information when that will be possible. Feel free to show your interest by adding your 👍 to this issue.

@guanchao-yang
Copy link

@guanchao-yang guanchao-yang commented May 14, 2019

@sdeleuze Thank you very much.

sdeleuze added a commit to spring-projects-experimental/spring-fu that referenced this issue May 14, 2019
sdeleuze added a commit to spring-projects-experimental/spring-fu that referenced this issue May 14, 2019
The focus is now on supporting GraalVM native image out of the box,
see #146 and spring-projects/spring-framework#22968 for more details.

Closes #198
jonasbark added a commit to feilfeilundfeil/spring-fu that referenced this issue May 14, 2019
…ecurity

* commit '056b2351a066b1741f7ce50e173c3d667dbf9357':
  Polishing
  Polishing
  Remove manual GraalVM native image configuration
  Focus GraalVM native image support on spring-projects/spring-framework#22968
  Avoid exposing AbstractDsl#initialize in public API
  Add tests for the R2DBC mapping to simple types
  Fix documentation
  Avoid specifying reactor-kotlin-extensions version
  Fix R2DBC transitive dependencies
@aclement
Copy link
Contributor Author

@aclement aclement commented May 14, 2019

More notes. The original comments were written based on the graal commit #818cccb852ec - and the spring boot sample referenced works against that level. Things have moved around in the GA release of graal and it needs updating, that is a work in progress right now (for example delay initialization is now flipped to specify what should be put through build time initialization instead).

@dheeraj29
Copy link

@dheeraj29 dheeraj29 commented May 15, 2019

@dsyer
Copy link
Member

@dsyer dsyer commented Jul 8, 2019

GraalVM has an issue resolving "directory" resources on the classpath (oracle/graal#1108). This may be a blocker for a large class of Spring apps (anything with a component scan, including entity scan). There are also issues with third party tools that do directory traversal in classpath resources (e.g. the issue above was raised by flyway). We are working around it in the @aclement feature jar for now by synthesising spring.components.

@ntrp
Copy link

@ntrp ntrp commented Jul 17, 2019

Thank you everyone for the effort, would be amazing to be able to scale down already working applications into micro-service level applications!

@yaakov-berkovitch
Copy link

@yaakov-berkovitch yaakov-berkovitch commented Sep 2, 2019

Perhaps you can tell me if there is a chance to make it working. I'm trying to create a native image for a spring boot application (spring boot 2.1.7 and spring 5.1.9), but got the following exception:

Warning: Aborting stand-alone image build. No instances of org.springframework.util.unit.DataSize are allowed in the image heap as this class should be initialized at image runtime.Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked.
Detailed message:
Trace: 	object org.springframework.util.unit.DataUnit
	method org.springframework.util.unit.DataSize.determineDataUnit(String, DataUnit)
Call path from entry point to org.springframework.util.unit.DataSize.determineDataUnit(String, DataUnit): 
	at org.springframework.util.unit.DataSize.determineDataUnit(DataSize.java:175)
	at org.springframework.util.unit.DataSize.parse(DataSize.java:165)
	at org.springframework.boot.convert.StringToDataSizeConverter.convert(StringToDataSizeConverter.java:57)
	at org.springframework.boot.convert.StringToDataSizeConverter.convert(StringToDataSizeConverter.java:48)
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:191)
	at org.springframework.boot.convert.DelimitedStringToCollectionConverter.lambda$convert$0(DelimitedStringToCollectionConverter.java:72)
	at org.springframework.boot.convert.DelimitedStringToCollectionConverter$$Lambda$403/680911666.apply(Unknown Source)
	at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:269)
	at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.util.stream.SpinedBuffer.forEach(SpinedBuffer.java:246)
	at java.util.stream.SpinedBuffer.toString(SpinedBuffer.java:269)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at com.oracle.svm.core.amd64.AMD64CPUFeatureAccess.verifyHostSupportsArchitecture(AMD64CPUFeatureAccess.java:179)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:129)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:186)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)

Any idea or workaround ? Do you think it may be a graal issue more than a spring issue ?

Thanks,
Yaakov

@evgzakharov
Copy link

@evgzakharov evgzakharov commented Sep 3, 2019

@yaakov-berkovitch I have same problem (with --static flag). Tried with spring boot 2.1 and 2.2.M5

@aclement
Copy link
Contributor Author

@aclement aclement commented Sep 3, 2019

@yaakov-berkovitch I will shortly be making available the version that works with Boot 2.2.0.M5 and the relevant framework 5.2. (this is with the recent Graal 19.2). I will then keep that up to date through the boot 2.2.0 release. That are changes in Spring I am relying on so I'm not sure I'd be expecting 2.1.7 to be working at the moment.

The issue you are showing is a Graal configuration issue. (So not graal, more that the config data being passed to graal for your spring app is not right - exactly what the spring-boot-graal-feature is designed to help with).

@sdeleuze sdeleuze removed this from the 5.x Backlog milestone Sep 9, 2019
@sdeleuze sdeleuze added this to the 5.3 M1 milestone Sep 9, 2019
@sdeleuze sdeleuze changed the title Create a Graal native-image support feature for Spring Improve GraalVM native support Jun 3, 2020
@sdeleuze
Copy link
Contributor

@sdeleuze sdeleuze commented Jun 3, 2020

With spring-graalvm-native 0.7.0 about to be released, I am turning this issue about a general GraalVM native support improvement one, with for now 4 more specific areas where we are going to improve Spring Framework support for GraalVM native:

  • #25151 Provide a flag to disable XML support
  • #25153 Provide a flag to disable SpEL support
  • #25179 Disable and remove unsupported features from native images
  • #25209 Add missing DispatcherServlet default beans to WebMvcConfigurationSupport

The bonus side effect of these changes is to remove as much substitution we can from spring-graalvm-native since they are by nature unmaintainable and not officially supported API.

Similar improvements could potentially be done at Spring Boot and Spring Data level (see for example spring-projects/spring-data-r2dbc#229).

The GraalVM feature and documentation will continue to mature on spring-graalvm-native side, see related roadmap.

@sdeleuze sdeleuze closed this Jun 20, 2020
sdeleuze added a commit to sdeleuze/spring-native that referenced this issue Jun 24, 2020
This commit also:
 - Remove Spring Framework substitutions (see spring-projects/spring-framework#22968 (comment) for more details)
 - Remove web-issue122 sample which seems not relevant anymore
 - Remove Unnecessary classes and resource configuration from spring-petclinic-jdbc sample
- Upgrade to spring Fu 0.4.0-SNAPSHOT
- Migrate remaining tests to JUnit 5
- Remove java.lang.management.* from hints
- Rename OnlyPresent to OnlyIfPresent

See spring-projects-experimentalgh-138
sdeleuze added a commit to spring-projects-experimental/spring-native that referenced this issue Jun 24, 2020
This commit also:
 - Remove Spring Framework substitutions (see spring-projects/spring-framework#22968 (comment) for more details)
 - Remove web-issue122 sample which seems not relevant anymore
 - Remove Unnecessary classes and resource configuration from spring-petclinic-jdbc sample
- Upgrade to spring Fu 0.4.0-SNAPSHOT
- Migrate remaining tests to JUnit 5
- Remove java.lang.management.* from hints
- Rename OnlyPresent to OnlyIfPresent

See gh-138
sdeleuze added a commit to spring-projects-experimental/spring-native that referenced this issue Jun 24, 2020
This commit also:
- Remove Spring Framework substitutions (see spring-projects/spring-framework#22968 (comment) for more details)
- Remove web-issue122 sample which seems not relevant anymore
- Remove Unnecessary classes and resource configuration from spring-petclinic-jdbc sample
- Upgrade to spring Fu 0.4.0-SNAPSHOT
- Migrate remaining tests to JUnit 5
- Remove java.lang.management.* from hints
- Rename OnlyPresent to OnlyIfPresent

See gh-138
dsyer pushed a commit to scratches/spring-graalvm-native that referenced this issue Sep 23, 2020
This commit also:
- Remove Spring Framework substitutions (see spring-projects/spring-framework#22968 (comment) for more details)
- Remove web-issue122 sample which seems not relevant anymore
- Remove Unnecessary classes and resource configuration from spring-petclinic-jdbc sample
- Upgrade to spring Fu 0.4.0-SNAPSHOT
- Migrate remaining tests to JUnit 5
- Remove java.lang.management.* from hints
- Rename OnlyPresent to OnlyIfPresent

See spring-projects-experimentalgh-138
dsyer pushed a commit to scratches/spring-graalvm-native that referenced this issue Sep 23, 2020
This commit also:
- Remove Spring Framework substitutions (see spring-projects/spring-framework#22968 (comment) for more details)
- Remove web-issue122 sample which seems not relevant anymore
- Remove Unnecessary classes and resource configuration from spring-petclinic-jdbc sample
- Upgrade to spring Fu 0.4.0-SNAPSHOT
- Migrate remaining tests to JUnit 5
- Remove java.lang.management.* from hints
- Rename OnlyPresent to OnlyIfPresent

See spring-projects-experimentalgh-138
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants