Refine container initialization and parallel startup logic

Update `TestcontainersLifecycleBeanPostProcessor` to restore early
container initialization logic and refine startup logic. Initial bean
access now again triggers the creation all container beans. In addition
the first access of a `Startable` bean now attempts to find and start
all other `Startable` beans.

Fixes gh-37989
This commit is contained in:
Phillip Webb 2023-10-25 11:53:04 -07:00
parent efb5cb0bcd
commit 0c66db7b18
1 changed files with 49 additions and 27 deletions

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@ -62,7 +63,9 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
private final TestcontainersStartup startup;
private volatile boolean containersInitialized = false;
private final AtomicBoolean startablesInitialized = new AtomicBoolean();
private final AtomicBoolean containersInitialized = new AtomicBoolean();
TestcontainersLifecycleBeanPostProcessor(ConfigurableListableBeanFactory beanFactory,
TestcontainersStartup startup) {
@ -72,38 +75,34 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!this.containersInitialized && this.beanFactory.isConfigurationFrozen()) {
if (this.beanFactory.isConfigurationFrozen() && this.containersInitialized.compareAndSet(false, true)) {
initializeContainers();
}
if (bean instanceof Startable startableBean) {
if (this.startablesInitialized.compareAndSet(false, true)) {
initializeStartables(startableBean, beanName);
}
else {
startableBean.start();
}
}
return bean;
}
private void initializeContainers() {
Set<String> beanNames = new LinkedHashSet<>();
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false)));
beanNames.addAll(List.of(this.beanFactory.getBeanNamesForType(Startable.class, false, false)));
initializeContainers(beanNames);
}
private void initializeContainers(Set<String> beanNames) {
List<Object> beans = new ArrayList<>(beanNames.size());
for (String beanName : beanNames) {
try {
beans.add(this.beanFactory.getBean(beanName));
}
catch (BeanCreationException ex) {
if (ex.contains(BeanCurrentlyInCreationException.class)) {
return;
}
throw ex;
}
private void initializeStartables(Startable startableBean, String startableBeanName) {
List<String> beanNames = new ArrayList<>(
List.of(this.beanFactory.getBeanNamesForType(Startable.class, false, false)));
beanNames.remove(startableBeanName);
List<Object> beans = getBeans(beanNames);
if (beans == null) {
this.startablesInitialized.set(false);
return;
}
if (!this.containersInitialized) {
this.containersInitialized = true;
if (!beanNames.isEmpty()) {
logger.debug(LogMessage.format("Initialized container beans '%s'", beanNames));
}
start(beans);
beanNames.add(startableBeanName);
beans.add(startableBean);
start(beans);
if (!beanNames.isEmpty()) {
logger.debug(LogMessage.format("Initialized and started startable beans '%s'", beanNames));
}
}
@ -115,6 +114,29 @@ class TestcontainersLifecycleBeanPostProcessor implements DestructionAwareBeanPo
this.startup.start(startables);
}
private void initializeContainers() {
List<String> beanNames = List.of(this.beanFactory.getBeanNamesForType(ContainerState.class, false, false));
if (getBeans(beanNames) == null) {
this.containersInitialized.set(false);
}
}
private List<Object> getBeans(List<String> beanNames) {
List<Object> beans = new ArrayList<>(beanNames.size());
for (String beanName : beanNames) {
try {
beans.add(this.beanFactory.getBean(beanName));
}
catch (BeanCreationException ex) {
if (ex.contains(BeanCurrentlyInCreationException.class)) {
return null;
}
throw ex;
}
}
return beans;
}
@Override
public boolean requiresDestruction(Object bean) {
return bean instanceof Startable;