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

Create a Graal native-image support feature for Spring #22968

Open
aclement opened this issue May 13, 2019 · 6 comments

Comments

Projects
None yet
6 participants
@aclement
Copy link
Contributor

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 working (messy) prototype of Spring Boot Feature in this project: https://github.com/aclement/spring-boot-graal-feature

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

commented May 14, 2019

@sdeleuze Thank you very much.

sdeleuze added a commit to spring-projects/spring-fu that referenced this issue May 14, 2019

sdeleuze added a commit to spring-projects/spring-fu that referenced this issue May 14, 2019

Remove manual GraalVM native image configuration
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

Merge commit '056b2351a066b1741f7ce50e173c3d667dbf9357' into spring-s…
…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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link

commented May 15, 2019

@dsyer

This comment has been minimized.

Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.