Skip to content
This repository was archived by the owner on Feb 23, 2023. It is now read-only.

AOT engine does not process Actuator child context #1146

Closed
To-da opened this issue Oct 11, 2021 · 6 comments
Closed

AOT engine does not process Actuator child context #1146

To-da opened this issue Oct 11, 2021 · 6 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@To-da
Copy link

To-da commented Oct 11, 2021

In https://start.spring.io the very basic app was created (Gradle + Kotlin + Boot (2.6.0.M3) + Spring Reactive Web + Native + Actuator). Nothing was changed apart from adding single entry to application.properties (management.server.port=8081).
Docker image was build by bootBuildImage Gradle task.

Result of running image (docker run --rm -p 8081:8081 -e "debug=true" springbugreports/nativebugreport) is following:

23:00:10.527 [main] INFO org.springframework.boot.SpringApplication - AOT mode enabled
2021-10-10 23:00:10.541  INFO 1 --- [           main] o.s.nativex.NativeListener               : This application is bootstrapped with code generated with Spring AOT

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.6.0-M3)

2021-10-10 23:00:10.546  INFO 1 --- [           main] o.s.boot.SpringApplication               : Starting application using Java 11.0.12 on ... with PID 1 (started by cnb in ...)
2021-10-10 23:00:10.546 DEBUG 1 --- [           main] o.s.boot.SpringApplication               : Running with Spring Boot v2.6.0-M3, Spring v5.3.10
2021-10-10 23:00:10.546  INFO 1 --- [           main] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2021-10-10 23:00:10.547 DEBUG 1 --- [           main] .r.c.ReactiveWebServerApplicationContext : Refreshing org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@30af9850
2021-10-10 23:00:10.564 DEBUG 1 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
2021-10-10 23:00:10.568  WARN 1 --- [           main] .r.c.ReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webHandler': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'healthEndpointWebFluxHandlerMapping': Unexpected exception during bean creation; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointFilter]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointFilter.<init>()
2021-10-10 23:00:10.570 DEBUG 1 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : Application failed to start due to an exception

java.lang.NoSuchMethodException: org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointFilter.<init>()
	at java.lang.Class.getConstructor0(DynamicHub.java:3349) ~[na:na]
	at java.lang.Class.getDeclaredConstructor(DynamicHub.java:2553) ~[na:na]
	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:25) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.isFilterMatch(EndpointDiscoverer.java:288) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.isExtensionExposed(EndpointDiscoverer.java:239) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.addExtensionBean(EndpointDiscoverer.java:170) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.addExtensionBeans(EndpointDiscoverer.java:159) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.discoverEndpoints(EndpointDiscoverer.java:124) ~[na:na]
	at org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer.getEndpoints(EndpointDiscoverer.java:117) ~[na:na]
	at org.springframework.boot.actuate.autoconfigure.health.HealthEndpointReactiveWebExtensionConfiguration$WebFluxAdditionalHealthEndpointPathsConfiguration.healthEndpointWebFluxHandlerMapping(HealthEndpointReactiveWebExtensionConfiguration.java:63) ~[com.bugreport.nativeBugReport.NativeBugReportApplicationKt:na]
	at org.springframework.boot.actuate.autoconfigure.health.ContextBootstrapInitializer.lambda$registerWebFluxAdditionalHealthEndpointPathsConfiguration_healthEndpointWebFluxHandlerMapping$14(ContextBootstrapInitializer.java:81) ~[na:na]
	at org.springframework.aot.beans.factory.ThrowableFunction.apply(ThrowableFunction.java:18) ~[na:na]
	at org.springframework.aot.beans.factory.InjectedElementResolver.create(InjectedElementResolver.java:51) ~[na:na]
	at org.springframework.aot.beans.factory.BeanDefinitionRegistrar$InstanceSupplierContext.create(BeanDefinitionRegistrar.java:168) ~[na:na]
	at org.springframework.boot.actuate.autoconfigure.health.ContextBootstrapInitializer.lambda$registerWebFluxAdditionalHealthEndpointPathsConfiguration_healthEndpointWebFluxHandlerMapping$15(ContextBootstrapInitializer.java:81) ~[na:na]
	at org.springframework.aot.beans.factory.ThrowableFunction.apply(ThrowableFunction.java:18) ~[na:na]
...

I wonder why this happened because native hints should be properly set by:
https://github.com/spring-projects-experimental/spring-native/blob/3987298796423bdf3c1ded009990003826681968/spring-native-configuration/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/servlet/CommonWebActuatorTypes.java#L58

And it's directly visible in reflect-config.json which contains:

 {
    "name": "org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointFilter",
    "allDeclaredConstructors": true,
    "allPublicMethods": true
  }

.zip file with project:
nativeBugReport.zip

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 11, 2021
@ttddyy
Copy link
Contributor

ttddyy commented Oct 14, 2021

The error occurs when a different management port is specified(management.server.port=8081).

I have followed the error messages and added this reflect-config.json:

[
  {
    "name": "org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointFilter",
    "methods": [
      {
        "name": "<init>",
        "parameterTypes": []
      }
    ]
  },
  {
    "name": "org.springframework.context.annotation.ConfigurationClassPostProcessor",
    "methods": [
      {
        "name": "<init>",
        "parameterTypes": []
      }
    ]
  }
]

However, it still complains at runtime:

org.springframework.context.ApplicationContextException: Failed to start bean 'webServerStartStop'; nested exception is java.lang.IllegalArgumentException: Attribute 'name' not found in attributes for annotation [org.springframework.context.annotation.Bean]
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[na:na]
	at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[na:na]
	at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[na:na]
	at java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
	at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[na:na]
	at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[na:na]
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:64) ~[na:na]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:719) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:401) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1279) ~[demo-webflux-actuator:2.6.0-M3]
	at com.example.demowebfluxactuator.DemoWebfluxActuatorApplication.main(DemoWebfluxActuatorApplication.java:10) ~[demo-webflux-actuator:0.0.1-SNAPSHOT]
Caused by: java.lang.IllegalArgumentException: Attribute 'name' not found in attributes for annotation [org.springframework.context.annotation.Bean]
	at org.springframework.util.Assert.notNull(Assert.java:219) ~[na:na]
	at org.springframework.core.annotation.AnnotationAttributes.assertAttributePresence(AnnotationAttributes.java:366) ~[na:na]
	at org.springframework.core.annotation.AnnotationAttributes.getRequiredAttribute(AnnotationAttributes.java:353) ~[na:na]
	at org.springframework.core.annotation.AnnotationAttributes.getStringArray(AnnotationAttributes.java:197) ~[na:na]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:205) ~[na:na]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:153) ~[na:na]
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:129) ~[na:na]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:343) ~[demo-webflux-actuator:5.3.10]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247) ~[demo-webflux-actuator:5.3.10]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311) ~[na:na]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[na:na]
	at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:64) ~[na:na]
	at org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration$DifferentManagementContextConfiguration.onApplicationEvent(ManagementContextAutoConfiguration.java:149) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration$DifferentManagementContextConfiguration.onApplicationEvent(ManagementContextAutoConfiguration.java:121) ~[demo-webflux-actuator:2.6.0-M3]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) ~[na:na]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) ~[na:na]
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) ~[na:na]
	at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) ~[na:na]
	at org.springframework.boot.web.reactive.context.WebServerManager.start(WebServerManager.java:57) ~[na:na]
	at org.springframework.boot.web.reactive.context.WebServerStartStopLifecycle.start(WebServerStartStopLifecycle.java:40) ~[na:na]
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[na:na]
	... 14 common frames omitted

It's strange asking @Bean annotation at this point. I haven't looked at the boot code for how management.server.port is handled. But I think some hints/behavior may be missing around this area.

Hope it helps to diagnose this issue.

@ttddyy
Copy link
Contributor

ttddyy commented Oct 15, 2021

I just looked at the Spring Boot code and stacktrace more carefully and the root cause was there.

When management port is different, ManagementContextAutoConfiguration matches @ConditionalOnManagementPort(ManagementPortType.DIFFERENT) condition.

Then, it creates a new application context via ManagementContextFactory(ReactiveManagementContextFactory for webflux).
This factory uses AnnotationConfigReactiveWebServerApplicationContext.

To make the issue bit more complicated, the management context registers EnableChildManagementContextConfiguration.
This class has @EnableManagementContext(ManagementContextType.CHILD) annotation and this brings in ManagementContextConfigurationImportSelector. This import selector loads entries from spring.factories and registers to the context. The loaded entries are configuration classes.

This process seems too dynamic for native runtime.

There are multiple ways to handle this issue.
With the new AOT engine, I think a natural way would be to register a different implementation of ManagementContextFactory bean; so that, the implementation could consider the closed world assumption, avoid metadata lookup, use supplier, etc, at runtime.

@snicoll
Copy link
Contributor

snicoll commented Oct 23, 2021

We don't support yet the child context for the actuator. We need to detect it, process the child context and generate the code like we do for the main context and then register a ManagementContextFactory that's going to use that rather than doing configuration class parsing at runtime.

@snicoll snicoll changed the title Problem with native reflection information for WebEndpointFilter constructor in WebFlux app AOT engine does not process Actuator child context Oct 23, 2021
@snicoll snicoll added theme: aot type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 23, 2021
@snicoll snicoll added this to the 0.11.x milestone Oct 23, 2021
@snicoll snicoll self-assigned this Oct 25, 2021
@snicoll
Copy link
Contributor

snicoll commented Oct 25, 2021

I've started looking into this and I've got a simple infrastructure that swaps the ManagementContextFactory by something that uses an AOT-generated context instead. The problem I am facing now is that BuildTimeBeanDefinitionsRegistrar does work on bean definitions, and registerBean isn't one, really. This is the mechanism that's used to feed the child context before it's refreshed.

@snicoll
Copy link
Contributor

snicoll commented Oct 26, 2021

Making progress with additional refinements to support this use case:

snicoll added a commit to snicoll/spring-native that referenced this issue Oct 27, 2021
This commit adds support for the separate application context that
Spring Boot creates when a management port is set. In AOT mode, the
context is processed at build time, exactly as the maine one is, and
the ManagementContextFactory implementation is replaced by an
AOT-specific one that loads the context from the generated code.

Closes spring-atticgh-1146
snicoll added a commit to snicoll/spring-native that referenced this issue Oct 27, 2021
This commit adds support for the separate application context that
Spring Boot creates when a management port is set. In AOT mode, the
context is processed at build time, exactly as the maine one is, and
the ManagementContextFactory implementation is replaced by an
AOT-specific one that loads the context from the generated code.

Closes spring-atticgh-1146
@snicoll
Copy link
Contributor

snicoll commented Oct 27, 2021

Alright, so this is basically implemented but we require a change in Spring Boot post RC for MVC, see spring-projects/spring-boot#28437. I'll park this until we can depend on 2.6.0.

@snicoll snicoll added the status: blocked An issue that's blocked on an external project change or another issue label Oct 27, 2021
@snicoll snicoll removed the status: blocked An issue that's blocked on an external project change or another issue label Nov 3, 2021
@snicoll snicoll modified the milestones: 0.11.0, 0.11.0-RC1 Nov 3, 2021
@snicoll snicoll closed this as completed in 18d5b85 Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: enhancement A general enhancement
Development

No branches or pull requests

4 participants