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

BeanCurrentlyInCreationException is thrown when multiple threads get a bean from StaticApplicationContext's Bean Factory #33463

Closed
mt-ocado opened this issue Sep 2, 2024 · 2 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@mt-ocado
Copy link

mt-ocado commented Sep 2, 2024

Affects: 6.2.0-M7

After updating to the latest milestone version (6.2.0-M7), an org.springframework.beans.factory.BeanCurrentlyInCreationException is thrown when getting a bean from beanFactory of StaticApplicationContext. Race conditions take place when multiple threads are involved.
Apparently, DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory) is the method where the following scenario takes place:

  1. beforeSingletonCreation(beanName) is called in Thread A,
  2. beforeSingletonCreation(beanName) is called in Thread B,
  3. Because afterSingletonCreation(String beanName) has not yet been called in Thread A, the above step will cause race condition and throwing of BeanCurrentlyInCreationException.

Notes

  • In version 6.1.11 the exception is not raised.
  • When using DefaultSingletonBeanRegistry directly instead of through StaticApplicationContext, then no exception is thrown.

Minimal example (might require several runs)

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticApplicationContext;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

class BeanFactoryRaceConditionTest {
    
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    @Test
    void testRaceCondition() {
        StaticApplicationContext applicationContext = new StaticApplicationContext();
        applicationContext.registerSingleton("book", Book.class);

        BeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();

        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                for (int j = 0; j < 1000; j++) {
                    beanFactory.getBean("book");
                }
            });
        }
        assertThrows(BeanCurrentlyInCreationException.class, () -> {
            for (int i = 0; i < 1000; i++) {
                beanFactory.getBean("book");
            }
        });
    }

    @Test
    void testNoRaceCondition() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("book", Book.class);

        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                for (int j = 0; j < 1000; j++) {
                    beanFactory.getBean("book");
                }
            });
        }

        assertDoesNotThrow(()->{
            for (int i = 0; i < 1000; i++) {
                beanFactory.getBean("book");
            }
        });
    }

    static class Book {
    }
}

Traces for testRaceCondition

Exception in thread "pool-1-thread-5" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'book': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:424)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:288)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at com.example.demo.BeanFactoryRaceConditionTest.lambda$testRaceCondition$0(BeanFactoryRaceConditionTest.java:23)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Sep 2, 2024
@bclozel bclozel added the in: core Issues in core modules (aop, beans, core, context, expression) label Sep 2, 2024
@liuao1004
Copy link

liuao1004 commented Sep 6, 2024

Hi, in your code, DefaultSingletonBeanRegistry#registerSingleton means register a bean in the IOC container but not the BeanDefinition. So, when getBean("book"), you will get the Book.class rather than an instance of Book.class which means IOC container does not create singleton bean.

As for the exception, see the source code at line 251 to 274 in DefaultSingletonBeanRegistry, As long as Thread-A acquires the singletonLock and it assigns itself to singletonCreationThread immediately, Thread-B get a not null value from this.singletonCreationThread,then Thread-B will not be blocked. Both of two threads will invoke method beforeSingletonCreation, the latter will throw an exception.

@jhoeller jhoeller self-assigned this Sep 9, 2024
@jhoeller jhoeller added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Sep 11, 2024
@jhoeller jhoeller added this to the 6.2.0-RC1 milestone Sep 11, 2024
@jhoeller
Copy link
Contributor

As of 6.2 RC1, we are applying the lenient locking fallback to the singleton pre-instantiation phase during a coordinated bootstrap only, exposing concurrent scenarios after bootstrap (e.g. for lazy singletons) to a full singleton lock. This covers the scenario 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: regression A bug that is also a regression
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants