Skip to content

Commit 4283a34

Browse files
committed
Merge branch '6.2.x'
2 parents 65cdbf7 + 4635419 commit 4283a34

File tree

2 files changed

+146
-27
lines changed

2 files changed

+146
-27
lines changed

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

+38-18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.beans.factory.support;
1818

1919
import java.util.Collections;
20+
import java.util.HashMap;
2021
import java.util.HashSet;
2122
import java.util.Iterator;
2223
import java.util.LinkedHashMap;
@@ -111,8 +112,11 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
111112
/** Names of beans that are currently in lenient creation. */
112113
private final Set<String> singletonsInLenientCreation = new HashSet<>();
113114

114-
/** Map from bean name to actual creation thread for leniently created beans. */
115-
private final Map<String, Thread> lenientCreationThreads = new ConcurrentHashMap<>();
115+
/** Map from one creation thread waiting on a lenient creation thread. */
116+
private final Map<Thread, Thread> lenientWaitingThreads = new HashMap<>();
117+
118+
/** Map from bean name to actual creation thread for currently created beans. */
119+
private final Map<String, Thread> currentCreationThreads = new ConcurrentHashMap<>();
116120

117121
/** Flag that indicates whether we're currently within destroySingletons. */
118122
private volatile boolean singletonsCurrentlyInDestruction = false;
@@ -251,9 +255,11 @@ public void addSingletonCallback(String beanName, Consumer<Object> singletonCons
251255
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
252256
Assert.notNull(beanName, "Bean name must not be null");
253257

258+
Thread currentThread = Thread.currentThread();
254259
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
255260
boolean acquireLock = !Boolean.FALSE.equals(lockFlag);
256261
boolean locked = (acquireLock && this.singletonLock.tryLock());
262+
257263
try {
258264
Object singletonObject = this.singletonObjects.get(beanName);
259265
if (singletonObject == null) {
@@ -305,17 +311,27 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
305311
this.lenientCreationLock.lock();
306312
try {
307313
while ((singletonObject = this.singletonObjects.get(beanName)) == null) {
314+
Thread otherThread = this.currentCreationThreads.get(beanName);
315+
if (otherThread != null && (otherThread == currentThread ||
316+
this.lenientWaitingThreads.get(otherThread) == currentThread)) {
317+
throw ex;
318+
}
308319
if (!this.singletonsInLenientCreation.contains(beanName)) {
309320
break;
310321
}
311-
if (this.lenientCreationThreads.get(beanName) == Thread.currentThread()) {
312-
throw ex;
322+
if (otherThread != null) {
323+
this.lenientWaitingThreads.put(currentThread, otherThread);
313324
}
314325
try {
315326
this.lenientCreationFinished.await();
316327
}
317328
catch (InterruptedException ie) {
318-
Thread.currentThread().interrupt();
329+
currentThread.interrupt();
330+
}
331+
finally {
332+
if (otherThread != null) {
333+
this.lenientWaitingThreads.remove(currentThread);
334+
}
319335
}
320336
}
321337
}
@@ -348,17 +364,12 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
348364
// Leniently created singleton object could have appeared in the meantime.
349365
singletonObject = this.singletonObjects.get(beanName);
350366
if (singletonObject == null) {
351-
if (locked) {
367+
this.currentCreationThreads.put(beanName, currentThread);
368+
try {
352369
singletonObject = singletonFactory.getObject();
353370
}
354-
else {
355-
this.lenientCreationThreads.put(beanName, Thread.currentThread());
356-
try {
357-
singletonObject = singletonFactory.getObject();
358-
}
359-
finally {
360-
this.lenientCreationThreads.remove(beanName);
361-
}
371+
finally {
372+
this.currentCreationThreads.remove(beanName);
362373
}
363374
newSingleton = true;
364375
}
@@ -408,6 +419,8 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
408419
this.lenientCreationLock.lock();
409420
try {
410421
this.singletonsInLenientCreation.remove(beanName);
422+
this.lenientWaitingThreads.entrySet().removeIf(
423+
entry -> entry.getValue() == currentThread);
411424
this.lenientCreationFinished.signalAll();
412425
}
413426
finally {
@@ -721,12 +734,19 @@ public void destroySingleton(String beanName) {
721734
// For an individual destruction, remove the registered instance now.
722735
// As of 6.2, this happens after the current bean's destruction step,
723736
// allowing for late bean retrieval by on-demand suppliers etc.
724-
this.singletonLock.lock();
725-
try {
737+
if (this.currentCreationThreads.get(beanName) == Thread.currentThread()) {
738+
// Local remove after failed creation step -> without singleton lock
739+
// since bean creation may have happened leniently without any lock.
726740
removeSingleton(beanName);
727741
}
728-
finally {
729-
this.singletonLock.unlock();
742+
else {
743+
this.singletonLock.lock();
744+
try {
745+
removeSingleton(beanName);
746+
}
747+
finally {
748+
this.singletonLock.unlock();
749+
}
730750
}
731751
}
732752
}

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

+108-9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.Timeout;
2121

22+
import org.springframework.beans.factory.BeanCreationException;
2223
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
2324
import org.springframework.beans.factory.ObjectProvider;
2425
import org.springframework.beans.factory.UnsatisfiedDependencyException;
@@ -42,7 +43,7 @@
4243
class BackgroundBootstrapTests {
4344

4445
@Test
45-
@Timeout(5)
46+
@Timeout(10)
4647
@EnabledForTestGroups(LONG_RUNNING)
4748
void bootstrapWithUnmanagedThread() {
4849
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class);
@@ -52,7 +53,7 @@ void bootstrapWithUnmanagedThread() {
5253
}
5354

5455
@Test
55-
@Timeout(5)
56+
@Timeout(10)
5657
@EnabledForTestGroups(LONG_RUNNING)
5758
void bootstrapWithUnmanagedThreads() {
5859
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadsBeanConfig.class);
@@ -64,7 +65,7 @@ void bootstrapWithUnmanagedThreads() {
6465
}
6566

6667
@Test
67-
@Timeout(5)
68+
@Timeout(10)
6869
@EnabledForTestGroups(LONG_RUNNING)
6970
void bootstrapWithStrictLockingThread() {
7071
SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME);
@@ -79,17 +80,26 @@ void bootstrapWithStrictLockingThread() {
7980
}
8081

8182
@Test
82-
@Timeout(5)
83+
@Timeout(10)
8384
@EnabledForTestGroups(LONG_RUNNING)
84-
void bootstrapWithCircularReference() {
85-
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CircularReferenceBeanConfig.class);
85+
void bootstrapWithCircularReferenceAgainstMainThread() {
86+
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CircularReferenceAgainstMainThreadBeanConfig.class);
8687
ctx.getBean("testBean1", TestBean.class);
8788
ctx.getBean("testBean2", TestBean.class);
8889
ctx.close();
8990
}
9091

9192
@Test
92-
@Timeout(5)
93+
@Timeout(10)
94+
@EnabledForTestGroups(LONG_RUNNING)
95+
void bootstrapWithCircularReferenceWithBlockingMainThread() {
96+
assertThatExceptionOfType(BeanCreationException.class)
97+
.isThrownBy(() -> new AnnotationConfigApplicationContext(CircularReferenceWithBlockingMainThreadBeanConfig.class))
98+
.withRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
99+
}
100+
101+
@Test
102+
@Timeout(10)
93103
@EnabledForTestGroups(LONG_RUNNING)
94104
void bootstrapWithCircularReferenceInSameThread() {
95105
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
@@ -98,7 +108,16 @@ void bootstrapWithCircularReferenceInSameThread() {
98108
}
99109

100110
@Test
101-
@Timeout(5)
111+
@Timeout(10)
112+
@EnabledForTestGroups(LONG_RUNNING)
113+
void bootstrapWithCircularReferenceInMultipleThreads() {
114+
assertThatExceptionOfType(BeanCreationException.class)
115+
.isThrownBy(() -> new AnnotationConfigApplicationContext(CircularReferenceInMultipleThreadsBeanConfig.class))
116+
.withRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
117+
}
118+
119+
@Test
120+
@Timeout(10)
102121
@EnabledForTestGroups(LONG_RUNNING)
103122
void bootstrapWithCustomExecutor() {
104123
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorBeanConfig.class);
@@ -202,7 +221,7 @@ public TestBean testBean2(ConfigurableListableBeanFactory beanFactory) {
202221

203222

204223
@Configuration(proxyBeanMethods = false)
205-
static class CircularReferenceBeanConfig {
224+
static class CircularReferenceAgainstMainThreadBeanConfig {
206225

207226
@Bean
208227
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
@@ -229,6 +248,46 @@ public TestBean testBean2(TestBean testBean1) {
229248
}
230249

231250

251+
@Configuration(proxyBeanMethods = false)
252+
static class CircularReferenceWithBlockingMainThreadBeanConfig {
253+
254+
@Bean
255+
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
256+
Thread thread = new Thread(testBean2::getObject);
257+
thread.setUncaughtExceptionHandler((t, ex) -> System.out.println(System.currentTimeMillis() + " " + ex + " " + t));
258+
thread.start();
259+
try {
260+
Thread.sleep(1000);
261+
}
262+
catch (InterruptedException ex) {
263+
throw new RuntimeException(ex);
264+
}
265+
return new TestBean(testBean2.getObject());
266+
}
267+
268+
@Bean
269+
public TestBean testBean2(ObjectProvider<TestBean> testBean1) {
270+
System.out.println(System.currentTimeMillis() + " testBean2 begin " + Thread.currentThread());
271+
try {
272+
Thread.sleep(2000);
273+
}
274+
catch (InterruptedException ex) {
275+
throw new RuntimeException(ex);
276+
}
277+
try {
278+
return new TestBean(testBean1.getObject());
279+
}
280+
catch (RuntimeException ex) {
281+
System.out.println(System.currentTimeMillis() + " testBean2 exception " + Thread.currentThread());
282+
throw ex;
283+
}
284+
finally {
285+
System.out.println(System.currentTimeMillis() + " testBean2 end " + Thread.currentThread());
286+
}
287+
}
288+
}
289+
290+
232291
@Configuration(proxyBeanMethods = false)
233292
static class CircularReferenceInSameThreadBeanConfig {
234293

@@ -262,6 +321,46 @@ public TestBean testBean3(TestBean testBean2) {
262321
}
263322

264323

324+
@Configuration(proxyBeanMethods = false)
325+
static class CircularReferenceInMultipleThreadsBeanConfig {
326+
327+
@Bean
328+
public TestBean testBean1(ObjectProvider<TestBean> testBean2, ObjectProvider<TestBean> testBean3) {
329+
new Thread(testBean2::getObject).start();
330+
new Thread(testBean3::getObject).start();
331+
try {
332+
Thread.sleep(2000);
333+
}
334+
catch (InterruptedException ex) {
335+
throw new RuntimeException(ex);
336+
}
337+
return new TestBean();
338+
}
339+
340+
@Bean
341+
public TestBean testBean2(ObjectProvider<TestBean> testBean3) {
342+
try {
343+
Thread.sleep(1000);
344+
}
345+
catch (InterruptedException ex) {
346+
throw new RuntimeException(ex);
347+
}
348+
return new TestBean(testBean3.getObject());
349+
}
350+
351+
@Bean
352+
public TestBean testBean3(ObjectProvider<TestBean> testBean2) {
353+
try {
354+
Thread.sleep(1000);
355+
}
356+
catch (InterruptedException ex) {
357+
throw new RuntimeException(ex);
358+
}
359+
return new TestBean(testBean2.getObject());
360+
}
361+
}
362+
363+
265364
@Configuration(proxyBeanMethods = false)
266365
static class CustomExecutorBeanConfig {
267366

0 commit comments

Comments
 (0)