Skip to content

Commit b07ff1c

Browse files
committed
Merge branch '6.2.x'
2 parents bb33a3e + b336bbe commit b07ff1c

File tree

2 files changed

+102
-7
lines changed

2 files changed

+102
-7
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Map;
2525
import java.util.Set;
2626
import java.util.concurrent.ConcurrentHashMap;
27+
import java.util.concurrent.locks.Condition;
2728
import java.util.concurrent.locks.Lock;
2829
import java.util.concurrent.locks.ReentrantLock;
2930
import java.util.function.Consumer;
@@ -101,6 +102,15 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
101102
/** Names of beans currently excluded from in creation checks. */
102103
private final Set<String> inCreationCheckExclusions = ConcurrentHashMap.newKeySet(16);
103104

105+
/** Specific lock for lenient creation tracking. */
106+
private final Lock lenientCreationLock = new ReentrantLock();
107+
108+
/** Specific lock condition for lenient creation tracking. */
109+
private final Condition lenientCreationFinished = this.lenientCreationLock.newCondition();
110+
111+
/** Names of beans that are currently in lenient creation. */
112+
private final Set<String> singletonsInLenientCreation = new HashSet<>();
113+
104114
/** Flag that indicates whether we're currently within destroySingletons. */
105115
private volatile boolean singletonsCurrentlyInDestruction = false;
106116

@@ -241,6 +251,7 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
241251
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
242252
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
243253
boolean locked = (acquireLock && this.singletonLock.tryLock());
254+
boolean lenient = false;
244255
try {
245256
Object singletonObject = this.singletonObjects.get(beanName);
246257
if (singletonObject == null) {
@@ -255,6 +266,14 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
255266
Thread.currentThread().getName() + "\" while other thread holds " +
256267
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
257268
}
269+
lenient = true;
270+
this.lenientCreationLock.lock();
271+
try {
272+
this.singletonsInLenientCreation.add(beanName);
273+
}
274+
finally {
275+
this.lenientCreationLock.unlock();
276+
}
258277
}
259278
else {
260279
// No specific locking indication (outside a coordinated bootstrap) and
@@ -283,7 +302,24 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
283302
}
284303
catch (BeanCurrentlyInCreationException ex) {
285304
if (locked) {
286-
throw ex;
305+
this.lenientCreationLock.lock();
306+
try {
307+
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
308+
if (!this.singletonsInLenientCreation.contains(beanName)) {
309+
throw ex;
310+
}
311+
try {
312+
this.lenientCreationFinished.await();
313+
}
314+
catch (InterruptedException ie) {
315+
Thread.currentThread().interrupt();
316+
}
317+
}
318+
return singletonObject;
319+
}
320+
finally {
321+
this.lenientCreationLock.unlock();
322+
}
287323
}
288324
// Try late locking for waiting on specific bean to be finished.
289325
this.singletonLock.lock();
@@ -337,6 +373,16 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
337373
if (locked) {
338374
this.singletonLock.unlock();
339375
}
376+
if (lenient) {
377+
this.lenientCreationLock.lock();
378+
try {
379+
this.singletonsInLenientCreation.remove(beanName);
380+
this.lenientCreationFinished.signalAll();
381+
}
382+
finally {
383+
this.lenientCreationLock.unlock();
384+
}
385+
}
340386
}
341387
}
342388

spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.Timeout;
2121

22-
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
2322
import org.springframework.beans.factory.ObjectProvider;
2423
import org.springframework.beans.testfixture.beans.TestBean;
2524
import org.springframework.context.ConfigurableApplicationContext;
2625
import org.springframework.core.testfixture.EnabledForTestGroups;
2726
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
2827

29-
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3028
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
3129
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
3230

@@ -42,8 +40,19 @@ class BackgroundBootstrapTests {
4240
void bootstrapWithUnmanagedThread() {
4341
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class);
4442
ctx.getBean("testBean1", TestBean.class);
45-
assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy( // late - not during refresh
46-
() -> ctx.getBean("testBean2", TestBean.class));
43+
ctx.getBean("testBean2", TestBean.class);
44+
ctx.close();
45+
}
46+
47+
@Test
48+
@Timeout(5)
49+
@EnabledForTestGroups(LONG_RUNNING)
50+
void bootstrapWithUnmanagedThreads() {
51+
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadsBeanConfig.class);
52+
ctx.getBean("testBean1", TestBean.class);
53+
ctx.getBean("testBean2", TestBean.class);
54+
ctx.getBean("testBean3", TestBean.class);
55+
ctx.getBean("testBean4", TestBean.class);
4756
ctx.close();
4857
}
4958

@@ -55,6 +64,7 @@ void bootstrapWithCustomExecutor() {
5564
ctx.getBean("testBean1", TestBean.class);
5665
ctx.getBean("testBean2", TestBean.class);
5766
ctx.getBean("testBean3", TestBean.class);
67+
ctx.getBean("testBean4", TestBean.class);
5868
ctx.close();
5969
}
6070

@@ -87,6 +97,45 @@ public TestBean testBean2() {
8797
}
8898

8999

100+
@Configuration(proxyBeanMethods = false)
101+
static class UnmanagedThreadsBeanConfig {
102+
103+
@Bean
104+
public TestBean testBean1(ObjectProvider<TestBean> testBean3, ObjectProvider<TestBean> testBean4) {
105+
new Thread(testBean3::getObject).start();
106+
new Thread(testBean4::getObject).start();
107+
try {
108+
Thread.sleep(1000);
109+
}
110+
catch (InterruptedException ex) {
111+
throw new RuntimeException(ex);
112+
}
113+
return new TestBean();
114+
}
115+
116+
@Bean
117+
public TestBean testBean2(TestBean testBean4) {
118+
return new TestBean(testBean4);
119+
}
120+
121+
@Bean
122+
public TestBean testBean3(TestBean testBean4) {
123+
return new TestBean(testBean4);
124+
}
125+
126+
@Bean
127+
public TestBean testBean4() {
128+
try {
129+
Thread.sleep(2000);
130+
}
131+
catch (InterruptedException ex) {
132+
throw new RuntimeException(ex);
133+
}
134+
return new TestBean();
135+
}
136+
}
137+
138+
90139
@Configuration(proxyBeanMethods = false)
91140
static class CustomExecutorBeanConfig {
92141

@@ -117,8 +166,8 @@ public TestBean testBean3() {
117166
}
118167

119168
@Bean
120-
public String dependent(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
121-
return "";
169+
public TestBean testBean4(@Lazy TestBean testBean1, @Lazy TestBean testBean2, @Lazy TestBean testBean3) {
170+
return new TestBean();
122171
}
123172
}
124173

0 commit comments

Comments
 (0)