Closed
Description
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 ofBeanCurrentlyInCreationException
.
Notes
- In version 6.1.11 the exception is not raised.
- When using
DefaultSingletonBeanRegistry
directly instead of throughStaticApplicationContext
, 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)