Skip to content

Auto-configure a bootstrapExecutor bean to be used by Framework's background bean initialization #39791

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

Closed
wilkinsona opened this issue Feb 28, 2024 · 12 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@wilkinsona
Copy link
Member

See spring-projects/spring-framework#13410 (comment)

@wilkinsona wilkinsona added the type: enhancement A general enhancement label Feb 28, 2024
@wilkinsona wilkinsona added this to the 3.x milestone Feb 28, 2024
@ballista01
Copy link

Hi @wilkinsona I'd like to contribute to the issue. However, I'm facing some issues.
Right now, the spring-boot master branch has a dependency springFrameworkVersion=6.1.5

springFrameworkVersion=6.1.5
, according to the comment you referred to, the @Bean(bootstrap=BACKGROUND) feature is introduced in spring framework 6.2.0. I tried to change the springboot's dependency to the spring framework to springFrameworkVersion=6.2.0-SNAPSHOT but got the following errors:

(base) wiz@wiz-ubuntu:~/Projects/uni/hands_on_refactor/spring-boot$ ./gradlew build -x test --refresh-dependencies

> Task :spring-boot-project:spring-boot-test:compileJava FAILED
/home/wiz/Projects/uni/hands_on_refactor/spring-boot/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.java:43: error: cannot access BrowserVersion
                super(enableJavascript);
                     ^
  class file for org.htmlunit.BrowserVersion not found
/home/wiz/Projects/uni/hands_on_refactor/spring-boot/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/htmlunit/webdriver/LocalHostWebConnectionHtmlUnitDriver.java:49: error: no suitable constructor found for WebConnectionHtmlUnitDriver(com.gargoylesoftware.htmlunit.BrowserVersion)
                super(browserVersion);
                ^
    constructor WebConnectionHtmlUnitDriver.WebConnectionHtmlUnitDriver(org.htmlunit.BrowserVersion) is not applicable
      (argument mismatch; com.gargoylesoftware.htmlunit.BrowserVersion cannot be converted to org.htmlunit.BrowserVersion)
    constructor WebConnectionHtmlUnitDriver.WebConnectionHtmlUnitDriver(boolean) is not applicable
      (argument mismatch; com.gargoylesoftware.htmlunit.BrowserVersion cannot be converted to boolean)
    constructor WebConnectionHtmlUnitDriver.WebConnectionHtmlUnitDriver(Capabilities) is not applicable
      (argument mismatch; com.gargoylesoftware.htmlunit.BrowserVersion cannot be converted to Capabilities)
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':spring-boot-project:spring-boot-test:compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.

* Get more help at https://help.gradle.org

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.6.4/userguide/command_line_interface.html#sec:command_line_warnings

BUILD FAILED in 13s
1225 actionable tasks: 154 executed, 1071 up-to-date

Is the current springboot version not ready for spring framework 6.2.0 yet? If so, should I develop this enhancement in spring framework?

@wilkinsona
Copy link
Member Author

Thanks for taking a look, @ballista01. We won't be able to work on this issue until we've started developing Spring Boot 3.4 and have upgraded to a Spring Framework 6.2 milestone or snapshot. This won't happen until some time after Spring Boot 3.3's release in May.

@YongGoose
Copy link
Contributor

@wilkinsona

I’m interested in this issue. May I ask if I can contribute to it?

@wilkinsona
Copy link
Member Author

Thanks for the offer, @YongGoose, but I'm not sure we know exactly how to implement this yet. Reading spring-projects/spring-framework#13410 (comment) again, there's the possibility of aliasing the application task executor to bootstrapExecutor but also perhaps a need to allow a boostrap-specific executor to be configured. We'll have to sync up with the Framework team to see if both of these options still make sense and then figure out how we might implement them in Boot.

@wilkinsona wilkinsona added the status: pending-design-work Needs design work before any code can be developed label Dec 4, 2024
@wilkinsona wilkinsona modified the milestones: 3.x, 3.5.x Dec 4, 2024
@YongGoose
Copy link
Contributor

@wilkinsona

I understand.
I would be grateful if you could let me know once the implementation direction is determined after discussing with the Spring team. 😁


ps. Additionally, there is another issue I am interested in. Would it be possible for me to contribute to this issue?

@wilkinsona
Copy link
Member Author

I think that one probably needs some design work too, unfortunately. Reading through the comments there still seems to be quite a bit that's undecided.

@mhalbritter
Copy link
Contributor

mhalbritter commented Apr 9, 2025

To tackle this, we could add a BeanFactoryPostProcessor to register an alias:

@Configuration(proxyBeanMethods = false)
static class BootstrapExecutorConfiguration {

	private static final String BOOTSTRAP_EXECUTOR_NAME = "bootstrapExecutor";

	@Bean
	static BeanFactoryPostProcessor bootstrapExecutorAliasPostProcessor() {
		return (beanFactory) -> {
			boolean hasBootstrapExecutor = beanFactory.containsBeanDefinition(BOOTSTRAP_EXECUTOR_NAME);
			boolean hasApplicationTaskExecutor = beanFactory.containsBeanDefinition(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
			if (!hasBootstrapExecutor && hasApplicationTaskExecutor) {
				beanFactory.registerAlias(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, BOOTSTRAP_EXECUTOR_NAME);
			}
		};
	}
}

this would alias applicationTaskExecutor to bootstrapExecutor, but only if there is an applicationTaskExecutor and the user hasn't defined their own bootstrapExecutor.

WDYT?

@mhalbritter
Copy link
Contributor

Or, without an alias but better for our configuration report:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "bootstrapExecutor")
@ConditionalOnBean(name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
static class BootstrapExecutorConfiguration {

	@Bean("bootstrapExecutor")
	Executor bootstrapExecutor(@Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) Executor applicationTaskExecutor) {
		return applicationTaskExecutor;
	}

}

@YongGoose
Copy link
Contributor

@wilkinsona @mhalbritter
I think the second approach is better.

Defining it as a Bean seems more flexible, especially if we need to modify the behavior of bootstrapExecutor or apply additional configurations later on.

If there are no strong objections, I’d like to go ahead and try implementing it. What do you think?

@nosan
Copy link
Contributor

nosan commented Apr 9, 2025

There is an important distinction between aliasing a bean and explicitly declaring a new bean. When you declare a bean using:

@Bean("bootstrapExecutor")
Executor bootstrapExecutor(
        @Qualifier(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME) Executor applicationTaskExecutor) {
    return applicationTaskExecutor;
}

The BeanFactory will contain two beans of type Executor, even though both beans refer to the
same instance. Consequently, calling beanFactory.getBean(Executor.class) results in the following
exception: No qualifying bean of type 'java.util.concurrent.Executor' available: expected single matching bean but found 2: bootstrapExecutor,applicationTaskExecutor

The second issue is that ThreadPoolTaskExecutor implements BeanNameAware. This means that when bootstrapExecutor is instantiated, setBeanName will be invoked with "bootstrapExecutor", overriding the previously set bean name "applicationTaskExecutor".

The third issue is that the init/shutdown operations will be invoked twice, once for applicationTaskExecutor and once for bootstrapExecutor because ThreadPoolTaskExecutor implements InitializingBean, DisposableBean.

 o.s.b.f.s.DefaultListableBeanFactory     : Invoking afterPropertiesSet() on bean with name 'applicationTaskExecutor'
 o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
 o.s.b.f.s.DefaultListableBeanFactory     : Finished creating instance of bean 'applicationTaskExecutor'
o.s.b.f.s.DefaultListableBeanFactory     : Autowiring by type from bean name 'bootstrapExecutor' via factory method to bean named 'applicationTaskExecutor'
o.s.b.f.s.DefaultListableBeanFactory     : Invoking afterPropertiesSet() on bean with name 'bootstrapExecutor'
o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'bootstrapExecutor'
o.s.b.f.support.DisposableBeanAdapter    : Invoking destroy() on bean with name 'bootstrapExecutor'
 o.s.b.f.support.DisposableBeanAdapter    : Invoking destroy() on bean with name 'applicationTaskExecutor'

Using the BeanFactoryPostProcessor (aliasing) approach avoids the issues described previously. However, it has a potential issue: if the bootstrapExecutor alias is already assigned to a different bean, it can result in exceptions like:
java.lang.IllegalStateException: Cannot define alias 'bootstrapExecutor' for name 'applicationTaskExecutor': It is already registered for name 'myExecutor'

The worse scenario, if the property spring.main.allow-bean-definition-overriding is set to true, it may silently override the existing alias.


This BFPP registers the alias bootstrapExecutor for the bean named applicationTaskExecutor only if the BeanFactory contains a bean named applicationTaskExecutor and does not contain one named bootstrapExecutor.

@Bean
static BeanFactoryPostProcessor bootstrapExecutorAliasBeanFactoryPostProcessor() {
    return (beanFactory) -> {
        if (beanFactory.containsBean("applicationTaskExecutor") && !beanFactory.containsBean("bootstrapExecutor")) {
            beanFactory.registerAlias("applicationTaskExecutor", "bootstrapExecutor");
        }
    };
}
Javadoc of containsBean

Does this bean factory contain a bean definition or externally registered singleton instance with the given name?
If the given name is an alias, it will be translated back to the corresponding canonical bean name.
If this factory is hierarchical, will ask any parent factory if the bean cannot be found in this factory instance.
If a bean definition or singleton instance matching the given name is found, this method will return true whether the named bean definition is concrete or abstract, lazy or eager, in scope or not. Therefore, note that a true return value from this method does not necessarily indicate that getBean will be able to obtain an instance for the same name.

@mhalbritter
Copy link
Contributor

@YongGoose i prefer the alias approach, too. Thanks @nosan for your investigation. Let's hold off the implementation for now, i want to talk to the team about it.

@mhalbritter mhalbritter added the for: team-meeting An issue we'd like to discuss as a team to make progress label Apr 10, 2025
@mhalbritter mhalbritter self-assigned this Apr 10, 2025
@philwebb
Copy link
Member

We discussed this today and we'd like to take #39791 (comment). Moritz has this on a branch so we should be able to merge that soon.

@philwebb philwebb removed status: pending-design-work Needs design work before any code can be developed for: team-meeting An issue we'd like to discuss as a team to make progress labels Apr 10, 2025
@mhalbritter mhalbritter modified the milestones: 3.5.x, 3.5.0-RC1 Apr 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

6 participants