Wait for lenient bean creation in locked thread when necessary
Closes gh-34349
This commit is contained in:
parent
056757b493
commit
b336bbe539
|
@ -24,6 +24,7 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -100,6 +101,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
/** Names of beans currently excluded from in creation checks. */
|
/** Names of beans currently excluded from in creation checks. */
|
||||||
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
|
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
|
||||||
|
|
||||||
|
/** Specific lock for lenient creation tracking. */
|
||||||
|
private final Lock lenientCreationLock = new ReentrantLock();
|
||||||
|
|
||||||
|
/** Specific lock condition for lenient creation tracking. */
|
||||||
|
private final Condition lenientCreationFinished = this.lenientCreationLock.newCondition();
|
||||||
|
|
||||||
|
/** Names of beans that are currently in lenient creation. */
|
||||||
|
private final Set<String> singletonsInLenientCreation = new HashSet<>();
|
||||||
|
|
||||||
/** 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;
|
||||||
|
|
||||||
|
@ -243,6 +253,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
|
||||||
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
|
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
|
||||||
boolean locked = (acquireLock && this.singletonLock.tryLock());
|
boolean locked = (acquireLock && this.singletonLock.tryLock());
|
||||||
|
boolean lenient = false;
|
||||||
try {
|
try {
|
||||||
Object singletonObject = this.singletonObjects.get(beanName);
|
Object singletonObject = this.singletonObjects.get(beanName);
|
||||||
if (singletonObject == null) {
|
if (singletonObject == null) {
|
||||||
|
@ -257,6 +268,14 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
Thread.currentThread().getName() + "\" while other thread holds " +
|
Thread.currentThread().getName() + "\" while other thread holds " +
|
||||||
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
|
||||||
}
|
}
|
||||||
|
lenient = true;
|
||||||
|
this.lenientCreationLock.lock();
|
||||||
|
try {
|
||||||
|
this.singletonsInLenientCreation.add(beanName);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.lenientCreationLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No specific locking indication (outside a coordinated bootstrap) and
|
// No specific locking indication (outside a coordinated bootstrap) and
|
||||||
|
@ -285,7 +304,24 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
}
|
}
|
||||||
catch (BeanCurrentlyInCreationException ex) {
|
catch (BeanCurrentlyInCreationException ex) {
|
||||||
if (locked) {
|
if (locked) {
|
||||||
throw ex;
|
this.lenientCreationLock.lock();
|
||||||
|
try {
|
||||||
|
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
|
||||||
|
if (!this.singletonsInLenientCreation.contains(beanName)) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.lenientCreationFinished.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return singletonObject;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.lenientCreationLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Try late locking for waiting on specific bean to be finished.
|
// Try late locking for waiting on specific bean to be finished.
|
||||||
this.singletonLock.lock();
|
this.singletonLock.lock();
|
||||||
|
@ -339,6 +375,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||||
if (locked) {
|
if (locked) {
|
||||||
this.singletonLock.unlock();
|
this.singletonLock.unlock();
|
||||||
}
|
}
|
||||||
|
if (lenient) {
|
||||||
|
this.lenientCreationLock.lock();
|
||||||
|
try {
|
||||||
|
this.singletonsInLenientCreation.remove(beanName);
|
||||||
|
this.lenientCreationFinished.signalAll();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.lenientCreationLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,12 @@ 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.testfixture.beans.TestBean;
|
import org.springframework.beans.testfixture.beans.TestBean;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.testfixture.EnabledForTestGroups;
|
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.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;
|
||||||
|
|
||||||
|
@ -42,8 +40,19 @@ class BackgroundBootstrapTests {
|
||||||
void bootstrapWithUnmanagedThread() {
|
void bootstrapWithUnmanagedThread() {
|
||||||
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class);
|
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class);
|
||||||
ctx.getBean("testBean1", TestBean.class);
|
ctx.getBean("testBean1", TestBean.class);
|
||||||
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy( // late - not during refresh
|
ctx.getBean("testBean2", TestBean.class);
|
||||||
() -> ctx.getBean("testBean2", TestBean.class));
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Timeout(5)
|
||||||
|
@EnabledForTestGroups(LONG_RUNNING)
|
||||||
|
void bootstrapWithUnmanagedThreads() {
|
||||||
|
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadsBeanConfig.class);
|
||||||
|
ctx.getBean("testBean1", TestBean.class);
|
||||||
|
ctx.getBean("testBean2", TestBean.class);
|
||||||
|
ctx.getBean("testBean3", TestBean.class);
|
||||||
|
ctx.getBean("testBean4", TestBean.class);
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +64,7 @@ class BackgroundBootstrapTests {
|
||||||
ctx.getBean("testBean1", TestBean.class);
|
ctx.getBean("testBean1", TestBean.class);
|
||||||
ctx.getBean("testBean2", TestBean.class);
|
ctx.getBean("testBean2", TestBean.class);
|
||||||
ctx.getBean("testBean3", TestBean.class);
|
ctx.getBean("testBean3", TestBean.class);
|
||||||
|
ctx.getBean("testBean4", TestBean.class);
|
||||||
ctx.close();
|
ctx.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +97,45 @@ class BackgroundBootstrapTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class UnmanagedThreadsBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean1(ObjectProvider<TestBean> testBean3, ObjectProvider<TestBean> testBean4) {
|
||||||
|
new Thread(testBean3::getObject).start();
|
||||||
|
new Thread(testBean4::getObject).start();
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean2(TestBean testBean4) {
|
||||||
|
return new TestBean(testBean4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean3(TestBean testBean4) {
|
||||||
|
return new TestBean(testBean4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean4() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
return new TestBean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class CustomExecutorBeanConfig {
|
static class CustomExecutorBeanConfig {
|
||||||
|
|
||||||
|
@ -117,8 +166,8 @@ class BackgroundBootstrapTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public String dependent(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
|
public TestBean testBean4(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
|
||||||
return "";
|
return new TestBean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue