This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new d775c350c7b CAUSEWAY-3937: GridCache to support recursive update
d775c350c7b is described below
commit d775c350c7bbf52a0cc93edcd7b5579760617ede
Author: Andi Huber <[email protected]>
AuthorDate: Sun Nov 2 08:48:51 2025 +0100
CAUSEWAY-3937: GridCache to support recursive update
should also fix random test failures
---
.../core/metamodel/services/grid/GridCache.java | 66 ++++++++++++++++++++--
1 file changed, 62 insertions(+), 4 deletions(-)
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
index 35813f48404..e566bb76a12 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/GridCache.java
@@ -18,7 +18,6 @@
*/
package org.apache.causeway.core.metamodel.services.grid;
-import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -35,21 +34,80 @@
*/
@Slf4j
record GridCache(
- Map<LayoutKey, Try<BSGrid>> gridsByKey) {
+ ConcurrentMapWrapper<LayoutKey, Try<BSGrid>> gridsByKey) {
public GridCache(final GridLoadingContext gridLoadingContext) {
- this(new ConcurrentHashMap<>());
+ this(new ConcurrentMapWrapper<>(new ConcurrentHashMap<>()));
}
/**
* To support metamodel invalidation/rebuilding of spec.
*/
public void remove(final Class<?> domainClass) {
-
gridsByKey.entrySet().removeIf(entry->entry.getKey().domainClass().equals(domainClass));
+
gridsByKey.map().entrySet().removeIf(entry->entry.getKey().domainClass().equals(domainClass));
}
public Try<BSGrid> computeIfAbsent(final LayoutKey layoutKey, final
Function<LayoutKey, Try<BSGrid>> factory) {
return gridsByKey.computeIfAbsent(layoutKey, factory);
}
+ /* when using ConcurrentHashMap directly we may see
+ java.lang.IllegalStateException: Recursive update
+ at
java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1779)
+ at
org.apache.causeway.core.metamodel.services.grid.GridCache.computeIfAbsent(GridCache.java:52)
+ at
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.load(GridServiceDefault.java:95)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.load(BSGridFacet.java:117)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.lambda$normalized$0(BSGridFacet.java:86)
+ at
java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.normalized(BSGridFacet.java:82)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.getGrid(BSGridFacet.java:68)
+ at
org.apache.causeway.core.metamodel.util.Facets.lambda$gridPreload$0(Facets.java:204)
+ at java.base/java.util.Optional.ifPresent(Optional.java:178)
+ at
org.apache.causeway.core.metamodel.util.Facets.gridPreload(Facets.java:200)
+ at
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.introspectFully(ObjectSpecificationDefault.java:640)
+ at
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.introspectUpTo(ObjectSpecificationDefault.java:615)
+ at
org.apache.causeway.core.metamodel.spec.impl.ObjectSpecificationDefault.streamDeclaredAssociations(ObjectSpecificationDefault.java:1000)
+ at
org.apache.causeway.core.metamodel.spec.impl.ObjectMemberContainer.streamAssociations(ObjectMemberContainer.java:127)
+ at
org.apache.causeway.core.metamodel.spec.impl.ObjectMemberContainer.streamAssociations(ObjectMemberContainer.java:128)
+ at
org.apache.causeway.core.metamodel.spec.feature.ObjectAssociationContainer.streamProperties(ObjectAssociationContainer.java:118)
+ at
org.apache.causeway.core.metamodel.services.grid.ObjectMemberResolverForGrid.validateAndNormalize(ObjectMemberResolverForGrid.java:148)
+ at
org.apache.causeway.core.metamodel.services.grid.ObjectMemberResolverForGrid.resolve(ObjectMemberResolverForGrid.java:115)
+ at
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.lambda$tryLoadNoCache$7(GridServiceDefault.java:110)
+ at java.base/java.util.Optional.orElseGet(Optional.java:364)
+ at
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.lambda$tryLoadNoCache$6(GridServiceDefault.java:110)
+ at
org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:63)
+ at
org.springframework.util.function.ThrowingFunction.apply(ThrowingFunction.java:51)
+ ..
+ at
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.tryLoadNoCache(GridServiceDefault.java:110)
+ at
java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1724)
+ at
org.apache.causeway.core.metamodel.services.grid.GridCache.computeIfAbsent(GridCache.java:52)
+ at
org.apache.causeway.core.metamodel.services.grid.GridServiceDefault.load(GridServiceDefault.java:95)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.load(BSGridFacet.java:117)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.lambda$normalized$0(BSGridFacet.java:86)
+ at
java.base/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1932)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.normalized(BSGridFacet.java:82)
+ at
org.apache.causeway.core.metamodel.facets.object.grid.BSGridFacet.getGrid(BSGridFacet.java:68)
+ ..
+ at
org.apache.causeway.core.metamodel.util.Facets.gridPreload(Facets.java:200)*/
+ record ConcurrentMapWrapper<K, V>(ConcurrentHashMap<K, V> map) {
+
+ public V computeIfAbsent(final K key, final
java.util.function.Function<? super K, ? extends V> mappingFunction) {
+ // Use an atomic flag to track whether a recursive call is
currently happening.
+ // It ensures that multiple threads can manage their recursion
state independently.
+ final ThreadLocal<Boolean> isRecursing =
ThreadLocal.withInitial(() -> false);
+
+ // Define a recursive compute function
+ return map.compute(key, (k, v) -> {
+ if (isRecursing.get()) return v; // return current value if
already in recursion
+
+ isRecursing.set(true); // mark as in recursion
+ try {
+ return v == null ? mappingFunction.apply(k) : v;
+ } finally {
+ isRecursing.remove(); // clean up the flag variable
+ }
+ });
+ }
+ }
+
}