From aeafa207276af368da9e5f590444816fbb6beca0 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 3 Sep 2024 12:07:33 -0700 Subject: [PATCH] Prevent 'Recursive update' exceptions with Restarter Update `Restarter` to prevent 'Recursive update' `IllegalStateException` from being thrown. Calls to `objectFactory.getObject()` now happen outside of `computeIfAbsent`. Fixes gh-41571 --- .../boot/devtools/restart/Restarter.java | 7 ++++++- .../boot/devtools/restart/RestarterTests.java | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java index 284056061943..4d71ceddd127 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/restart/Restarter.java @@ -440,7 +440,12 @@ private LeakSafeThread getLeakSafeThread() { } public Object getOrAddAttribute(String name, final ObjectFactory objectFactory) { - return this.attributes.computeIfAbsent(name, (ignore) -> objectFactory.getObject()); + Object value = this.attributes.get(name); + if (value == null) { + value = objectFactory.getObject(); + this.attributes.put(name, value); + } + return value; } public Object removeAttribute(String name) { diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java index 75689433fff6..ed1dd1bb3a17 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/restart/RestarterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -145,15 +145,28 @@ void addClassLoaderFiles() { } @Test - @SuppressWarnings("rawtypes") void getOrAddAttributeWithExistingAttribute() { Restarter.getInstance().getOrAddAttribute("x", () -> "abc"); - ObjectFactory objectFactory = mock(ObjectFactory.class); + ObjectFactory objectFactory = mock(ObjectFactory.class); Object attribute = Restarter.getInstance().getOrAddAttribute("x", objectFactory); assertThat(attribute).isEqualTo("abc"); then(objectFactory).shouldHaveNoInteractions(); } + @Test + void getOrAddAttributeWithRecursion() { + Restarter restarter = Restarter.getInstance(); + Object added = restarter.getOrAddAttribute("postgresContainer", () -> { + restarter.getOrAddAttribute("rabbitContainer", () -> "def"); + return "abc"; + }); + ObjectFactory objectFactory = mock(ObjectFactory.class); + assertThat(added).isEqualTo("abc"); + assertThat(restarter.getOrAddAttribute("postgresContainer", objectFactory)).isEqualTo("abc"); + assertThat(restarter.getOrAddAttribute("rabbitContainer", objectFactory)).isEqualTo("def"); + then(objectFactory).shouldHaveNoInteractions(); + } + @Test void getThreadFactory() throws Exception { final ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();