Enforce circular reference exception within non-managed thread
Closes gh-34672
This commit is contained in:
@@ -110,6 +110,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
|||||||
/** Names of beans that are currently in lenient creation. */
|
/** Names of beans that are currently in lenient creation. */
|
||||||
private final Set<String> singletonsInLenientCreation = new HashSet<>();
|
private final Set<String> singletonsInLenientCreation = new HashSet<>();
|
||||||
|
|
||||||
|
/** Map from bean name to actual creation thread for leniently created beans. */
|
||||||
|
private final Map<String, Thread> lenientCreationThreads = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** Flag that indicates whether we're currently within destroySingletons. */
|
/** Flag that indicates whether we're currently within destroySingletons. */
|
||||||
private volatile boolean singletonsCurrentlyInDestruction = false;
|
private volatile boolean singletonsCurrentlyInDestruction = false;
|
||||||
|
|
||||||
@@ -307,6 +310,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
|||||||
if (!this.singletonsInLenientCreation.contains(beanName)) {
|
if (!this.singletonsInLenientCreation.contains(beanName)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (this.lenientCreationThreads.get(beanName) == Thread.currentThread()) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.lenientCreationFinished.await();
|
this.lenientCreationFinished.await();
|
||||||
}
|
}
|
||||||
@@ -344,7 +350,18 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
|||||||
// Leniently created singleton object could have appeared in the meantime.
|
// Leniently created singleton object could have appeared in the meantime.
|
||||||
singletonObject = this.singletonObjects.get(beanName);
|
singletonObject = this.singletonObjects.get(beanName);
|
||||||
if (singletonObject == null) {
|
if (singletonObject == null) {
|
||||||
singletonObject = singletonFactory.getObject();
|
if (locked) {
|
||||||
|
singletonObject = singletonFactory.getObject();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.lenientCreationThreads.put(beanName, Thread.currentThread());
|
||||||
|
try {
|
||||||
|
singletonObject = singletonFactory.getObject();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.lenientCreationThreads.remove(beanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
newSingleton = true;
|
newSingleton = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ package org.springframework.context.annotation;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.Timeout;
|
import org.junit.jupiter.api.Timeout;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
import org.springframework.beans.testfixture.beans.TestBean;
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
@@ -29,6 +31,7 @@ import org.springframework.core.testfixture.EnabledForTestGroups;
|
|||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
|
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
|
||||||
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
|
||||||
|
|
||||||
@@ -85,6 +88,15 @@ class BackgroundBootstrapTests {
|
|||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(5)
|
||||||
|
@EnabledForTestGroups(LONG_RUNNING)
|
||||||
|
void bootstrapWithCircularReferenceInSameThread() {
|
||||||
|
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
|
||||||
|
.isThrownBy(() -> new AnnotationConfigApplicationContext(CircularReferenceInSameThreadBeanConfig.class))
|
||||||
|
.withRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Timeout(5)
|
@Timeout(5)
|
||||||
@EnabledForTestGroups(LONG_RUNNING)
|
@EnabledForTestGroups(LONG_RUNNING)
|
||||||
@@ -179,7 +191,7 @@ class BackgroundBootstrapTests {
|
|||||||
catch (InterruptedException ex) {
|
catch (InterruptedException ex) {
|
||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
}
|
}
|
||||||
return new TestBean();
|
return new TestBean("testBean1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -217,6 +229,39 @@ class BackgroundBootstrapTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CircularReferenceInSameThreadBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
|
||||||
|
new Thread(testBean2::getObject).start();
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean2(TestBean testBean3) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean3(TestBean testBean2) {
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class CustomExecutorBeanConfig {
|
static class CustomExecutorBeanConfig {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user