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

Asynchronous initialization of beans during startup [SPR-14920] #19487

Closed
spring-projects-issues opened this issue Nov 18, 2016 · 3 comments
Closed
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Nov 18, 2016

Antonio Anzivino opened SPR-14920 and commented

Some projects take a few minutes to start up because of I/O operations required for their initialization. For example, one of my projects queries a European Central Bank web service as part of its init sequence, while other beans query the database to retrieve the initial data set.

In general, Spring initializes beans one by one on the same thread and it has been working safe so far.

It is quite a few years I was thinking to this: make bean initialization asynchronous.

I imagine the process like this: if a bean's initialization method (AsyncInitializingBean?) returns a Future<Void>, then Spring uses the default task executor to defer bean initialization and go to the next bean. Only when all the Futures return, the context is said to have initialized.

If a bean has a dependency on an asynchronous bean, then obviously its initialization cannot start before the dependent asynchronous bean has initialized.

I am opening this ticket to get feedback from the community.

And now let's talk about the workaround, because there is one. A developer can speed up the context init process by manually deferring I/O operations after init, but then the developer has to make sure that beans calling it in an uninitialized state (before the deferred initialization completes) get a consistent result, e.g. implementing locks on all methods. This works but requires a lot more code.

Example:

public class AsyncWorkaround1Bean {

    private boolean inited = false;

    @PostConstruct
    public void init() {
        this.taskExecutor.submit(() -> doAsyncInit());
    }


    private void doAsyncInit() {
        ........
        inited = true;
    }


    public List getData() {
         if (!inited) throw new NotReadyYetException();
    }
}

public class AsyncWorkaround2Bean {

    private boolean inited = false;
    private final Object lock = new Object();

    @PostConstruct
    public void init() {
        this.taskExecutor.submit(() -> doAsyncInit());
    }


    private void doAsyncInit() {
        ........
        inited = true;
        synchronized(lock) {
            lock.notifyAll();
        }
    }


    public List getData() {
        while (!inited)
            synchronized(lock) {
                lock.wait;
            }
    }
}

Thanks for your feedback


Issue Links:

2 votes, 4 watchers

@spring-projects-issues
Copy link
Collaborator Author

Antonio Anzivino commented

Thanks for making progress on this. I will add additional comment

An @Autowired dependency targeting an asynchronous bean could, in my mind, be set immediately as a proxy to the asynchronous bean. If enough threads start in parallel to initialize beans, any reference to a bean that is asynchronous and injected could result in a call to Future.get(timeout) by the lazy proxy.

This introduced all kinds of hell for bean deadlocking, but is a very simple approach and can be configured by means of parallelism and timeout to address the deadlocking issues.

After all, Spring already detects circular dependencies, and timeouts are widely used in detecting a deadlock.

@sdeleuze
Copy link
Contributor

sdeleuze commented Oct 5, 2019

This issue also happens with Coroutines when they are used with runBlocking(). It works with GlobalScope.launch() but native support of @PostConstruct suspend fun init() would be less error prone and cleaner from a programming model POV.

@jhoeller jhoeller modified the milestones: 5.x Backlog, 5.3 M1 Oct 7, 2019
@jhoeller jhoeller modified the milestones: 5.3 M1, 5.3 M2 Feb 24, 2020
@jhoeller jhoeller modified the milestones: 5.3 M2, 5.x Backlog Jul 3, 2020
@jhoeller jhoeller modified the milestones: 5.x Backlog, General Backlog Aug 24, 2020
@jhoeller jhoeller modified the milestones: General Backlog, 6.x Backlog Jul 12, 2023
@jhoeller jhoeller modified the milestones: 6.x Backlog, 6.2.0-M1 Feb 19, 2024
@jhoeller
Copy link
Contributor

See #13410 (comment) for a variant of this to be introduced in 6.2, at the granularity of the entire bean creation phase for a particular bean.

The new background initialization flag can be set for individual beans. Injection points are not proxies by default but can be declared as @Lazy (or ObjectProvider<...>) in combination with such background-bootstrapped target beans. Also, the common currently-in-creation check applies as usual, as well as a couple of specific checks that enforce a clean dependency order.

Alternatively, there is still the pattern of bean-internal asynchronous bootstrapping, along the lines of what LocalContainerEntityManagerFactoryBean does for JPA. An asynchronous init method within a synchronous bean creation phase does not fit with Spring's model, this is better off handled internal to each bean as suggested above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants