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

Limit external configuration sources parsing in native-mode #42140

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

zakkak
Copy link
Contributor

@zakkak zakkak commented Jul 25, 2024

SmallryeConfigBuilder tries to access properties files from various sources, despite them not being available at run-time nor supported in native-mode, see #41994

This patch also avoids getting a MissingRegistrationError when using -H:+ThrowMissingRegistrationErrors or --exact-reachability-metadata.

Related to #41994 and #41995

Supersedes #42103

@zakkak
Copy link
Contributor Author

zakkak commented Jul 25, 2024

@radcortez please take a look. I opened it as draft since there are way more reflective access being caught by -H:ThrowMissingRegistrationErrors=

The ones I am trying to understand now are ones generated because of the sources automatically added to the RunTimeConfigCustomizer. Do you think we could/should skip some of those when targeting native-mode, or are they all necessary?

Since I have your attention I would also like to ask why you are not generating ServiceProviderBuildItems instead of ReflectiveClassBuildItems in

reflectiveClass.produce(ReflectiveClassBuildItem.builder(service).build());

Thanks

@radcortez
Copy link
Member

The ones I am trying to understand now are ones generated because of the sources automatically added to the RunTimeConfigCustomizer. Do you think we could/should skip some of those when targeting native-mode, or are they all necessary?

The idea is to discover all the services during the build and generate a config customizer that programmatically registers all the discovered services. I think we cannot skip any of the things we discover. Are you seeing issues with this?

Since I have your attention I would also like to ask why you are not generating ServiceProviderBuildItems instead of ReflectiveClassBuildItems in

We probably don't need the reflection registration anymore. We can try to drop it and check :)

@@ -391,6 +392,17 @@ public void setupConfigOverride(

@BuildStep
public void watchConfigFiles(BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFiles) {
Copy link
Member

@radcortez radcortez Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The watched files include external files like .env and {user.dir}/config/application{-profile}.properties. Should these be registered as a resource with the native image?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything accessed through java.lang.ClassLoader.getResources (invoked by https://github.com/smallrye/smallrye-common/blob/c29bb1c00f484f5fa86f183e75c37af462ac6546/classloader/src/main/java/io/smallrye/common/classloader/ClassPathUtils.java#L84) needs to be registered (at least in theory).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these "issues" are popping up when using -H:+ThrowMissingRegistrationErrors (see #41995) which aims to help developers detect unregistered accesses to elements. What I suspect is that there are many places where Quarkus tries to access unregistered elements and just fails without a warning, but since there are other places or ways to get the same data back it works as expected.

So in many of these cases it might be OK to not register the elements, but at the same time the best practice would be to ensure hat Quarkus also doesn't try to access them at run-time (e.g. through substitutions or changing configuration).

List<String> configWatchedFiles = new ArrayList<>();
String userDir = System.getProperty("user.dir");

// Main files
configWatchedFiles.add("META-INF/microprofile-config.yaml");
configWatchedFiles.add("META-INF/microprofile-config.yml");
configWatchedFiles.add("application.yaml");
configWatchedFiles.add("application.yml");
configWatchedFiles.add(Paths.get(userDir, "config", "application.yaml").toAbsolutePath().toString());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check previous comments about external files.

public class FileSystemOnlySourcesConfigBuilder implements ConfigBuilder {
@Override
public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) {
return builder.withSources(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to disable the default sources which include the classpath lookup and then add the inFileSystem: builder.setAddDefaultSources(false).addSystemSources().withSources(inFileSystem...)

public SmallRyeConfigBuilder configBuilder(final SmallRyeConfigBuilder builder) {
return builder.withSources(
inFileSystem(Paths.get(System.getProperty("user.dir"), "config", "application.properties").toUri().toString(),
260, Thread.currentThread().getContextClassLoader()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the classloader set in the builder: io.smallrye.config.SmallRyeConfigBuilder#getClassLoader.

@zakkak
Copy link
Contributor Author

zakkak commented Jul 26, 2024

The idea is to discover all the services during the build and generate a config customizer that programmatically registers all the discovered services. I think we cannot skip any of the things we discover. Are you seeing issues with this?

Not necessarily, it kind of complicates the resource registration process so I am just trying to see if there is any opportunity for trimming down :)

@zakkak
Copy link
Contributor Author

zakkak commented Jul 26, 2024

We probably don't need the reflection registration anymore. We can try to drop it and check :)

It looks like you are right.

`SmallryeConfigBuilder` tries to access properties files from various
sources, despite them not being available at run-time nor supported in
native-mode, see quarkusio#41994

This patch also avoids getting a `MissingRegistrationError` when using
`-H:+ThrowMissingRegistrationErrors` or `--exact-reachability-metadata`.

Related to quarkusio#41994 and
quarkusio#41995
@zakkak zakkak force-pushed the 2024-07-24-smallryeconfigsources branch from fc678ed to 628ee63 Compare July 26, 2024 09:54
@radcortez
Copy link
Member

Not necessarily, it kind of complicates the resource registration process so I am just trying to see if there is any opportunity for trimming down :)

Hum, the goal was to avoid as many registrations as possible. Since this has been an ongoing process, there are probably some code leftovers that are not required anymore (the reflection registration of services, for instance).

Let me know what you are seeing and we try to hax a few things.

@radcortez
Copy link
Member

I think we can drop registrations of ServiceProvider in

// XXX replace this with constant-folded service loader impl
@BuildStep
void nativeServiceProviders(
final BuildProducer<ServiceProviderBuildItem> providerProducer) throws IOException {
providerProducer.produce(new ServiceProviderBuildItem(ConfigProviderResolver.class.getName(),
SmallRyeConfigProviderResolver.class.getName()));
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
for (Class<?> serviceClass : Arrays.asList(
Converter.class,
ConfigSourceInterceptor.class,
ConfigSourceInterceptorFactory.class,
SecretKeysHandler.class,
SecretKeysHandlerFactory.class,
ConfigValidator.class)) {
final String serviceName = serviceClass.getName();
final Set<String> names = ServiceUtil.classNamesNamedIn(classLoader, SERVICES_PREFIX + serviceName);
if (!names.isEmpty()) {
providerProducer.produce(new ServiceProviderBuildItem(serviceName, names));
}
}
}

for everything except ConfigValidator which we don't generate yet.

@zakkak
Copy link
Contributor Author

zakkak commented Jul 26, 2024

Let me know what you are seeing and we try to hax a few things.

If you run:

./mvnw -Dnative -pl integration-tests/smallrye-config/ -Dnative.surefire.skip -Dformat.skip -Dno-descriptor-tests clean verify -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-22 -Dquarkus.native.additional-build-args=-H:ThrowMissingRegistrationErrors=,-H:MissingRegistrationReportingMode=Warn

you will see a bunch of exceptions being thrown, indicating that the resulting native-image is trying to access elements that have not been registered at build time. Unfortunately the Warn mode limits the trace to just 4 lines, so you can't get enough details from it. FYI I am attaching the output of the above command when using a modified Mandrel build without the 4 lines limit.

exceptions.log

A couple of indicative examples:

com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError: The program tried to access the resource at path 

   META-INF/microprofile-config.yaml

 without it being registered as reachable. Add it to the resource metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#resources-and-resource-bundles for help
  java.base@22.0.1-beta/java.lang.ClassLoader.getResources(ClassLoader.java:99)
  io.smallrye.common.classloader.ClassPathUtils.consumeAsPaths(ClassPathUtils.java:84)
  io.smallrye.config.AbstractLocationConfigSourceLoader.tryClassPath(AbstractLocationConfigSourceLoader.java:141)
  io.smallrye.config.AbstractLocationConfigSourceLoader.loadConfigSources(AbstractLocationConfigSourceLoader.java:104)
  io.smallrye.config.AbstractLocationConfigSourceLoader.loadConfigSources(AbstractLocationConfigSourceLoader.java:87)
  io.smallrye.config.source.yaml.YamlConfigSourceLoader$InClassPath.getConfigSources(YamlConfigSourceLoader.java:37)
  io.smallrye.config.source.yaml.YamlConfigSourceLoader$InClassPath.getConfigSources(YamlConfigSourceLoader.java:31)
  io.smallrye.config.SmallRyeConfig$ConfigSources.buildSources(SmallRyeConfig.java:832)
  io.smallrye.config.SmallRyeConfig$ConfigSources.<init>(SmallRyeConfig.java:770)
  io.smallrye.config.SmallRyeConfig.<init>(SmallRyeConfig.java:85)
  io.smallrye.config.SmallRyeConfigBuilder.build(SmallRyeConfigBuilder.java:736)
  io.quarkus.runtime.generated.Config.readConfig(Unknown Source)
  io.quarkus.runtime.generated.Config.createRunTimeConfig(Unknown Source)
  io.quarkus.deployment.steps.RuntimeConfigSetup.deploy(Unknown Source)
  io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
  io.quarkus.runtime.Application.start(Application.java:101)
  io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
  io.quarkus.runner.GeneratedMain.main(Unknown Source)
com.oracle.svm.core.jdk.resources.MissingResourceRegistrationError: The program tried to access the resource at path 
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

   io.quarkus.datasource.runtime.DataSourcesRuntimeConfig.getDeclaredMethods()

 without it being registered for runtime reflection. Add io.quarkus.datasource.runtime.DataSourcesRuntimeConfig.getDeclaredMethods() to the reflection metadata to solve this problem. See https://www.graalvm.org/latest/reference-manual/native-image/metadata/#reflection for help.
  java.base@22.0.1-beta/java.lang.Class.getDeclaredMethods(DynamicHub.java:1165)
  io.smallrye.config.ConfigMappingInterface.createConfigurationInterface(ConfigMappingInterface.java:757)
  io.smallrye.config.ConfigMappingInterface$1.computeValue(ConfigMappingInterface.java:37)
  io.smallrye.config.ConfigMappingInterface$1.computeValue(ConfigMappingInterface.java:35)
  java.base@22.0.1-beta/java.lang.ClassValue.get(JavaLangSubstitutions.java:531)
  io.smallrye.config.ConfigMappingInterface.getConfigurationInterface(ConfigMappingInterface.java:52)
  io.smallrye.config.validator.BeanValidationConfigValidator.validateMapping(BeanValidationConfigValidator.java:38)
  io.smallrye.config.SmallRyeConfig.getConfigMapping(SmallRyeConfig.java:604)
  io.quarkus.deployment.steps.DataSourcesExcludedFromHealthChecksProcessor$produceBean807665441.deploy_0(Unknown Source)
  io.quarkus.deployment.steps.DataSourcesExcludedFromHealthChecksProcessor$produceBean807665441.deploy(Unknown Source)
  io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
  io.quarkus.runtime.Application.start(Application.java:101)
  io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:111)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:71)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:44)
  io.quarkus.runtime.Quarkus.run(Quarkus.java:124)
  io.quarkus.runner.GeneratedMain.main(Unknown Source)
org.graalvm.nativeimage.MissingReflectionRegistrationError: The program tried to reflectively access

@radcortez
Copy link
Member

If you run:

./mvnw -Dnative -pl integration-tests/smallrye-config/ -Dnative.surefire.skip -Dformat.skip -Dno-descriptor-tests clean verify -Dquarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-22 -Dquarkus.native.additional-build-args=-H:ThrowMissingRegistrationErrors=,-H:MissingRegistrationReportingMode=Warn

you will see a bunch of exceptions being thrown, indicating that the resulting native-image is trying to access elements that have not been registered at build time. Unfortunately the Warn mode limits the trace to just 4 lines, so you can't get enough details from it. FYI I am attaching the output of the above command when using a modified Mandrel build without the 4 lines limit.

Great. Thanks.

Ok, let's try to trim these down. Did you run with these PR changes? It shouldn't be looking for *.properties files in the classpath.

@zakkak
Copy link
Contributor Author

zakkak commented Jul 29, 2024

Did you run with these PR changes?

Yes, it removes some of these warnings but not all.

@radcortez
Copy link
Member

Yes, it removes some of these warnings but not all.

Yes, some of the warnings related to the lookups to the config files should be gone now.

Looking into other warnings, I see many related to the Validator. The issue here is that we only register for reflection the config classes that have validation annotations. The config code still calls getDeclaredMethods to no effect because there are no properties to validate, so it doesn't really fail.

There is no quick fix for this. I guess the best option is to override the Validator and pass in a list of config classes that require validation to avoid that call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants