From 74ab5e4e255e2c089ace10af8a5e689ce3eadebe Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 7 Apr 2025 22:37:19 +0200 Subject: [PATCH 1/2] Enforce circular reference exception between more than two threads as well See gh-34672 --- .../support/DefaultSingletonBeanRegistry.java | 12 +++- .../annotation/BackgroundBootstrapTests.java | 64 ++++++++++--------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 00867c7d415..056481a86db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -315,7 +315,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements while ((singletonObject = this.singletonObjects.get(beanName)) == null) { Thread otherThread = this.currentCreationThreads.get(beanName); if (otherThread != null && (otherThread == currentThread || - this.lenientWaitingThreads.get(otherThread) == currentThread)) { + checkDependentWaitingThreads(otherThread, currentThread))) { throw ex; } if (!this.singletonsInLenientCreation.contains(beanName)) { @@ -431,6 +431,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements } } + private boolean checkDependentWaitingThreads(Thread waitingThread, Thread candidateThread) { + Thread threadToCheck = waitingThread; + while ((threadToCheck = this.lenientWaitingThreads.get(threadToCheck)) != null) { + if (threadToCheck == candidateThread) { + return true; + } + } + return false; + } + /** * Determine whether the current thread is allowed to hold the singleton lock. *

By default, any thread may acquire and hold the singleton lock, except diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java index 3d7662ec44e..a0731445314 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java @@ -139,7 +139,7 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -150,7 +150,7 @@ class BackgroundBootstrapTests { Thread.sleep(2000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -170,7 +170,7 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -191,7 +191,7 @@ class BackgroundBootstrapTests { Thread.sleep(2000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -208,7 +208,7 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean("testBean1"); } @@ -230,7 +230,7 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -241,7 +241,7 @@ class BackgroundBootstrapTests { Thread.sleep(2000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -253,37 +253,25 @@ class BackgroundBootstrapTests { @Bean public TestBean testBean1(ObjectProvider testBean2) { - Thread thread = new Thread(testBean2::getObject); - thread.setUncaughtExceptionHandler((t, ex) -> System.out.println(System.currentTimeMillis() + " " + ex + " " + t)); - thread.start(); + new Thread(testBean2::getObject).start(); try { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(testBean2.getObject()); } @Bean public TestBean testBean2(ObjectProvider testBean1) { - System.out.println(System.currentTimeMillis() + " testBean2 begin " + Thread.currentThread()); try { Thread.sleep(2000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - try { - return new TestBean(testBean1.getObject()); - } - catch (RuntimeException ex) { - System.out.println(System.currentTimeMillis() + " testBean2 exception " + Thread.currentThread()); - throw ex; - } - finally { - System.out.println(System.currentTimeMillis() + " testBean2 end " + Thread.currentThread()); + Thread.currentThread().interrupt(); } + return new TestBean(testBean1.getObject()); } } @@ -298,7 +286,7 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -309,7 +297,7 @@ class BackgroundBootstrapTests { Thread.sleep(2000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -325,14 +313,17 @@ class BackgroundBootstrapTests { static class CircularReferenceInMultipleThreadsBeanConfig { @Bean - public TestBean testBean1(ObjectProvider testBean2, ObjectProvider testBean3) { + public TestBean testBean1(ObjectProvider testBean2, ObjectProvider testBean3, + ObjectProvider testBean4) { + new Thread(testBean2::getObject).start(); new Thread(testBean3::getObject).start(); + new Thread(testBean4::getObject).start(); try { - Thread.sleep(2000); + Thread.sleep(3000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(); } @@ -343,18 +334,29 @@ class BackgroundBootstrapTests { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(testBean3.getObject()); } @Bean - public TestBean testBean3(ObjectProvider testBean2) { + public TestBean testBean3(ObjectProvider testBean4) { + try { + Thread.sleep(1000); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + return new TestBean(testBean4.getObject()); + } + + @Bean + public TestBean testBean4(ObjectProvider testBean2) { try { Thread.sleep(1000); } catch (InterruptedException ex) { - throw new RuntimeException(ex); + Thread.currentThread().interrupt(); } return new TestBean(testBean2.getObject()); } From ffd15155ee21cb166306d698c4dd4ea8e35d0c8c Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 7 Apr 2025 22:41:45 +0200 Subject: [PATCH 2/2] Upgrade to Mockito 5.17 and Checkstyle 10.23 --- .../java/org/springframework/build/CheckstyleConventions.java | 2 +- framework-platform/framework-platform.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java index 6b9e022fee3..4216ae6fa21 100644 --- a/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/CheckstyleConventions.java @@ -50,7 +50,7 @@ public class CheckstyleConventions { project.getPlugins().apply(CheckstylePlugin.class); project.getTasks().withType(Checkstyle.class).forEach(checkstyle -> checkstyle.getMaxHeapSize().set("1g")); CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class); - checkstyle.setToolVersion("10.22.0"); + checkstyle.setToolVersion("10.23.0"); checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle")); String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies(); diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index c92a1cb0eac..b41bf656521 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -21,7 +21,7 @@ dependencies { api(platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1")) api(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3")) api(platform("org.junit:junit-bom:5.12.1")) - api(platform("org.mockito:mockito-bom:5.16.1")) + api(platform("org.mockito:mockito-bom:5.17.0")) constraints { api("com.fasterxml:aalto-xml:1.3.2")