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:
beforeSingletonCreation(beanName) is called in Thread A,
beforeSingletonCreation(beanName) is called in Thread B,
- 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)
Affects: 6.2.0-M7
After updating to the latest milestone version (6.2.0-M7), an
org.springframework.beans.factory.BeanCurrentlyInCreationExceptionis thrown when getting a bean frombeanFactoryofStaticApplicationContext. 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:beforeSingletonCreation(beanName)is called in Thread A,beforeSingletonCreation(beanName)is called in Thread B,afterSingletonCreation(String beanName)has not yet been called in Thread A, the above step will cause race condition and throwing ofBeanCurrentlyInCreationException.Notes
DefaultSingletonBeanRegistrydirectly instead of throughStaticApplicationContext, then no exception is thrown.Minimal example (might require several runs)
Traces for
testRaceCondition