Description
After upgrading to spring boot 3.4 and spring 6.2.0 we sometimes see a BeanCurrentlyInCreationException in our logs after application startup.
Looking into it, this seems to be happening when multiple threads simultaneously first create a FactoryBean (in our application we are using spring mvc and this happens when multiple concurrent http requests happen after startup where a BeanCurrentlyInCreationException
is thrown for a request scoped bean).
This was working fine in spring 6.1.15 and is broken in 6.2.0.
From a quick look, I would suspect that this is related to changes in this issue where locking in FactoryBeanRegistrySupport was removed.
The following is an example test (inspired by the example of this issue) which runs without any exception in 6.1.15 but does throw an exception in 6.2.0
package org.springframework.beans.factory.xml;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.support.StaticApplicationContext;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class BeanFactoryRaceConditionTest {
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Test
void testRaceCondition() {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("book", BookFactory.class);
List<Future<?>> allFutures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
var future = executorService.submit(() -> {
for (int j = 0; j < 1000; j++) {
try {
Book book = applicationContext.getBean(Book.class);
Assertions.assertThat(book).isNotNull();
} catch (BeanCurrentlyInCreationException e) {
throw new RuntimeException(e);
}
}
});
allFutures.add(future);
}
Assertions.assertThatCode(() -> {
for (Future<?> future : allFutures) {
future.get();
}
}).doesNotThrowAnyException();
}
static class Book {
}
static class BookFactory implements FactoryBean<Book> {
@Override
public Book getObject() {
return new Book();
}
@Override
public Class<?> getObjectType() {
return Book.class;
}
}
}