Skip to content

Commit 10fb28d

Browse files
committed
fix(dsl): use memoization to address performance issues
1 parent 102ef32 commit 10fb28d

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

annotations/dsl/src/main/java/io/sundr/dsl/internal/graph/functions/Nodes.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@
3434
import java.util.Collection;
3535
import java.util.Collections;
3636
import java.util.Comparator;
37+
import java.util.HashMap;
3738
import java.util.LinkedHashSet;
3839
import java.util.List;
40+
import java.util.Map;
3941
import java.util.Set;
42+
import java.util.concurrent.ConcurrentHashMap;
4043
import java.util.function.Function;
44+
import java.util.stream.Collectors;
4145

4246
import io.sundr.adapter.apt.AptContext;
4347
import io.sundr.dsl.internal.graph.Node;
@@ -74,8 +78,31 @@ public Set<Node<TypeDef>> apply(Set<TypeDef> clazzes) {
7478
}
7579
};
7680

81+
private static final int MAX_TREE_DEPTH = 1000;
82+
private static final Map<String, Node<TypeDef>> treeCache = new ConcurrentHashMap<>();
83+
84+
private static String cacheKey(NodeContext context) {
85+
String visitedNames = context.getVisited().stream()
86+
.map(TypeDef::getName)
87+
.sorted()
88+
.collect(Collectors.joining(","));
89+
return context.getItem().getName() + "|" + visitedNames;
90+
}
91+
7792
public static final Function<NodeContext, Node<TypeDef>> TO_TREE = new Function<NodeContext, Node<TypeDef>>() {
7893
public Node<TypeDef> apply(NodeContext context) {
94+
String key = cacheKey(context);
95+
Node<TypeDef> cached = treeCache.get(key);
96+
if (cached != null) {
97+
return cached;
98+
}
99+
100+
int depth = context.getPath().size();
101+
if (depth > MAX_TREE_DEPTH) {
102+
throw new IllegalStateException("DSL tree depth exceeded " + MAX_TREE_DEPTH + ". Path: " +
103+
context.getPath().stream().map(t -> t.getName()).collect(Collectors.joining(" -> ")));
104+
}
105+
79106
Set<Node<TypeDef>> nextVertices = new LinkedHashSet<Node<TypeDef>>();
80107

81108
//visited and path are the same only in the first iteration. see bellow:
@@ -94,7 +121,10 @@ public Node<TypeDef> apply(NodeContext context) {
94121
nextVertices.add(subGraph);
95122
}
96123
}
97-
return DslContextManager.getContext().getNodeRepository().getOrCreateNode(context.getItem(), nextVertices);
124+
Node<TypeDef> result = DslContextManager.getContext().getNodeRepository().getOrCreateNode(context.getItem(),
125+
nextVertices);
126+
treeCache.put(key, result);
127+
return result;
98128
}
99129
};
100130

@@ -320,14 +350,22 @@ public Set<String> scopeKeywords(Collection<TypeDef> clazzes) {
320350
private static class CandidateComparator implements Comparator<TypeDef> {
321351

322352
private final NodeContext nodeContext;
353+
private final Map<TypeDef, Set<TypeDef>> nextCache = new HashMap<TypeDef, Set<TypeDef>>();
323354

324355
private CandidateComparator(NodeContext nodeContext) {
325356
this.nodeContext = nodeContext;
326357
}
327358

359+
private Set<TypeDef> getNextFor(TypeDef candidate) {
360+
if (!nextCache.containsKey(candidate)) {
361+
nextCache.put(candidate, TO_NEXT.apply(nodeContext.contextOfChild(candidate).addToVisited(candidate).build()));
362+
}
363+
return nextCache.get(candidate);
364+
}
365+
328366
public int compare(TypeDef left, TypeDef right) {
329-
Set<TypeDef> leftSet = TO_NEXT.apply(nodeContext.contextOfChild(left).addToVisited(left).build());
330-
Set<TypeDef> rightSet = TO_NEXT.apply(nodeContext.contextOfChild(right).addToVisited(right).build());
367+
Set<TypeDef> leftSet = getNextFor(left);
368+
Set<TypeDef> rightSet = getNextFor(right);
331369
if (leftSet.contains(right) && rightSet.contains(left)) {
332370
return 0;
333371
} else if (leftSet.contains(right)) {

annotations/dsl/src/main/java/io/sundr/dsl/internal/utils/GraphUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public static boolean isSatisfied(TypeDef candidate, List<TypeDef> path) {
7676
} else if (multiple && lastIndex > 0 && lastIndex < path.size() - 1) {
7777
//We only accept repetition of the last element. Other wise we can end up in infinite loops
7878
return false;
79+
} else if (multiple && lastIndex == path.size() - 1) {
80+
//Prevent infinite repetition: if @Multiple item is already at the end, don't add it again
81+
return false;
7982
}
8083
return filter.apply(path);
8184
}

0 commit comments

Comments
 (0)