Revert use of Map::computeIfAbsent in thread and tx scopes

Issues gh-25038 and gh-25618 collectively introduced a regression for
thread-scoped and transaction-scoped beans.

For example, given a thread-scoped bean X that depends on another
thread-scoped bean Y, if the names of the beans (when used as map keys)
end up in the same bucket within a ConcurrentHashMap AND an attempt is
made to retrieve bean X from the ApplicationContext prior to retrieving
bean Y, then the use of Map::computeIfAbsent in SimpleThreadScope
results in recursive access to the same internal bucket in the map.

On Java 8, that scenario simply hangs. On Java 9 and higher,
ConcurrentHashMap throws an IllegalStateException pointing out that a
"Recursive update" was attempted.

In light of these findings, we are reverting the changes made to
SimpleThreadScope and SimpleTransactionScope in commits 50a4fdac6e and
148dc95eb1.

Closes gh-25801
This commit is contained in:
Sam Brannen
2020-09-25 10:55:32 +02:00
parent a532c527dd
commit f5d36aa47a
4 changed files with 34 additions and 12 deletions

View File

@@ -16,9 +16,9 @@
package org.springframework.transaction.support;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
@@ -50,7 +50,14 @@ public class SimpleTransactionScope implements Scope {
TransactionSynchronizationManager.registerSynchronization(new CleanupSynchronization(scopedObjects));
TransactionSynchronizationManager.bindResource(this, scopedObjects);
}
return scopedObjects.scopedInstances.computeIfAbsent(name, k -> objectFactory.getObject());
// NOTE: Do NOT modify the following to use Map::computeIfAbsent. For details,
// see https://github.com/spring-projects/spring-framework/issues/25801.
Object scopedObject = scopedObjects.scopedInstances.get(name);
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
scopedObjects.scopedInstances.put(name, scopedObject);
}
return scopedObject;
}
@Override
@@ -92,7 +99,7 @@ public class SimpleTransactionScope implements Scope {
*/
static class ScopedObjectsHolder {
final Map<String, Object> scopedInstances = new ConcurrentHashMap<>();
final Map<String, Object> scopedInstances = new HashMap<>();
final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<>();
}