Skip to content

Commit 5f71dd7

Browse files
committed
Bug 39023812 - [39023803->14.1.2.0.6] OutOfMemoryError when using ArrayFilter
[git-p4: depot-paths = "//dev/coherence-ce/release/coherence-ce-v14.1.2.0/": change = 119072]
1 parent b077992 commit 5f71dd7

File tree

3 files changed

+320
-14
lines changed

3 files changed

+320
-14
lines changed

prj/coherence-core/src/main/java/com/tangosol/util/filter/ArrayFilter.java

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates.
2+
* Copyright (c) 2000, 2026, Oracle and/or its affiliates.
33
*
44
* Licensed under the Universal Permissive License v 1.0 as shown at
55
* https://oss.oracle.com/licenses/upl.
@@ -123,7 +123,14 @@ public boolean evaluateEntry(Map.Entry entry)
123123
*/
124124
public Filter applyIndex(Map mapIndexes, Set setKeys)
125125
{
126-
return applyIndex(mapIndexes, setKeys, null, null);
126+
try
127+
{
128+
return applyIndex(mapIndexes, setKeys, null, null);
129+
}
130+
finally
131+
{
132+
clearOptimizedFiltersRecursive();
133+
}
127134
}
128135

129136

@@ -134,18 +141,25 @@ public Filter applyIndex(Map mapIndexes, Set setKeys)
134141
*/
135142
public void explain(QueryContext ctx, QueryRecord.PartialResult.ExplainStep step, Set setKeys)
136143
{
137-
optimizeFilterOrder(ctx.getBackingMapContext().getIndexMap(), setKeys);
138-
139-
Filter<?>[] aFilter = getFilters();
140-
for (Filter filter : aFilter)
144+
try
141145
{
142-
QueryRecord.PartialResult.ExplainStep subStep = step.ensureStep(filter);
146+
optimizeFilterOrder(ctx.getBackingMapContext().getIndexMap(), setKeys);
147+
148+
Filter<?>[] aFilter = getFilters();
149+
for (Filter filter : aFilter)
150+
{
151+
QueryRecord.PartialResult.ExplainStep subStep = step.ensureStep(filter);
143152

144-
QueryRecorderFilter filterRecorder = filter instanceof QueryRecorderFilter
145-
? (QueryRecorderFilter) filter
146-
: new WrapperQueryRecorderFilter(filter);
153+
QueryRecorderFilter filterRecorder = filter instanceof QueryRecorderFilter
154+
? (QueryRecorderFilter) filter
155+
: new WrapperQueryRecorderFilter(filter);
147156

148-
filterRecorder.explain(ctx, subStep, setKeys);
157+
filterRecorder.explain(ctx, subStep, setKeys);
158+
}
159+
}
160+
finally
161+
{
162+
clearOptimizedFiltersRecursive();
149163
}
150164
}
151165

@@ -158,7 +172,15 @@ public Filter trace(QueryContext ctx, QueryRecord.PartialResult.TraceStep step,
158172

159173
long ldtStart = System.currentTimeMillis();
160174

161-
Filter filterRemaining = applyIndex(ctx.getBackingMapContext().getIndexMap(), setKeys, ctx, step);
175+
Filter filterRemaining;
176+
try
177+
{
178+
filterRemaining = applyIndex(ctx.getBackingMapContext().getIndexMap(), setKeys, ctx, step);
179+
}
180+
finally
181+
{
182+
clearOptimizedFiltersRecursive();
183+
}
162184

163185
long ldtEnd = System.currentTimeMillis();
164186

@@ -230,6 +252,11 @@ protected abstract boolean evaluateEntry(Map.Entry entry,
230252
*/
231253
public Filter<?>[] getFilters()
232254
{
255+
if (m_fOptimized)
256+
{
257+
return m_aFilter;
258+
}
259+
233260
Filter<?>[] filters = f_aFilterOptimized.get();
234261
return filters == null ? m_aFilter : filters;
235262
}
@@ -242,6 +269,7 @@ public Filter<?>[] getFilters()
242269
public void honorOrder()
243270
{
244271
m_fOptimized = true;
272+
clearOptimizedFilters();
245273
}
246274

247275

@@ -256,7 +284,7 @@ public void honorOrder()
256284
*/
257285
protected void optimizeFilterOrder(Map mapIndexes, Set setKeys)
258286
{
259-
if (m_fOptimized)
287+
if (m_fOptimized || f_aFilterOptimized.get() != null)
260288
{
261289
return;
262290
}
@@ -288,7 +316,35 @@ protected void optimizeFilterOrder(Map mapIndexes, Set setKeys)
288316
}
289317

290318
f_aFilterOptimized.set(aFilter);
291-
m_fOptimized = true;
319+
}
320+
321+
/**
322+
* Clear optimized filter order for the current thread.
323+
*/
324+
protected void clearOptimizedFilters()
325+
{
326+
f_aFilterOptimized.remove();
327+
}
328+
329+
/**
330+
* Clear optimized filter order recursively for this filter and any nested
331+
* {@link ArrayFilter} instances.
332+
*/
333+
protected void clearOptimizedFiltersRecursive()
334+
{
335+
clearOptimizedFilters();
336+
337+
Filter<?>[] aFilter = m_aFilter;
338+
if (aFilter != null)
339+
{
340+
for (Filter<?> filter : aFilter)
341+
{
342+
if (filter instanceof ArrayFilter)
343+
{
344+
((ArrayFilter) filter).clearOptimizedFiltersRecursive();
345+
}
346+
}
347+
}
292348
}
293349

294350
/**

prj/test/unit/coherence-tests/src/test/java/com/tangosol/util/filter/AllFilterTest.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
import com.tangosol.util.ValueExtractor;
1313
import com.tangosol.util.extractor.IdentityExtractor;
1414

15+
import java.lang.reflect.Field;
16+
1517
import java.util.Map;
1618
import java.util.HashMap;
1719
import java.util.Set;
1820
import java.util.HashSet;
1921
import java.util.Arrays;
2022
import java.util.Collection;
23+
import java.util.concurrent.atomic.AtomicInteger;
2124

2225
import org.junit.Test;
2326
import org.junit.runner.RunWith;
@@ -96,6 +99,127 @@ public void testApplyIndex()
9699
assertTrue("key2 should remain.", setKeys.contains("key2"));
97100
}
98101

102+
@Test
103+
public void shouldReuseOptimizedOrderBetweenCalculateAndApply()
104+
{
105+
MapIndex index = new SimpleMapIndex(IdentityExtractor.INSTANCE, false, null, null);
106+
107+
Map<String, Object> map = Map.of(
108+
"key1", 1,
109+
"key2", 2,
110+
"key3", 3,
111+
"key4", 4
112+
);
113+
map.entrySet().forEach(index::insert);
114+
115+
Map<ValueExtractor, MapIndex> mapIndexes = new HashMap<>();
116+
mapIndexes.put(IdentityExtractor.INSTANCE, index);
117+
118+
CountingFilter counting = new CountingFilter(1000);
119+
AllFilter filter = new AllFilter(new Filter[]
120+
{
121+
counting,
122+
new EqualsFilter(IdentityExtractor.INSTANCE, 2)
123+
});
124+
125+
Set<String> setKeys = new HashSet<>(map.keySet());
126+
127+
filter.calculateEffectiveness(mapIndexes, setKeys);
128+
filter.applyIndex(mapIndexes, new HashSet<>(setKeys));
129+
130+
assertEquals("calculateEffectiveness should be reused between calls.", 1, counting.getCalculateCount());
131+
}
132+
133+
@Test
134+
public void shouldClearNestedOptimizedThreadLocalsAfterApply()
135+
throws Exception
136+
{
137+
MapIndex index = new SimpleMapIndex(IdentityExtractor.INSTANCE, false, null, null);
138+
139+
Map<String, Object> map = Map.of(
140+
"key1", 1,
141+
"key2", 2,
142+
"key3", 3,
143+
"key4", 4
144+
);
145+
map.entrySet().forEach(index::insert);
146+
147+
Map<ValueExtractor, MapIndex> mapIndexes = new HashMap<>();
148+
mapIndexes.put(IdentityExtractor.INSTANCE, index);
149+
150+
AnyFilter filterNested = new AnyFilter(new Filter[]
151+
{
152+
new EqualsFilter(IdentityExtractor.INSTANCE, 2),
153+
new EqualsFilter(IdentityExtractor.INSTANCE, 3)
154+
});
155+
156+
AllFilter filterRoot = new AllFilter(new Filter[]
157+
{
158+
filterNested,
159+
new EqualsFilter(IdentityExtractor.INSTANCE, 99)
160+
});
161+
162+
Set<String> setKeys = new HashSet<>(map.keySet());
163+
164+
filterNested.calculateEffectiveness(mapIndexes, setKeys);
165+
assertNotNull("Nested optimized order should be set before apply.", getOptimizedThreadLocal(filterNested).get());
166+
167+
filterRoot.applyIndex(mapIndexes, setKeys);
168+
169+
assertNull("Root optimized order should be cleared.", getOptimizedThreadLocal(filterRoot).get());
170+
assertNull("Nested optimized order should be cleared.", getOptimizedThreadLocal(filterNested).get());
171+
}
172+
173+
private static ThreadLocal<?> getOptimizedThreadLocal(ArrayFilter filter)
174+
throws Exception
175+
{
176+
Field field = ArrayFilter.class.getDeclaredField("f_aFilterOptimized");
177+
field.setAccessible(true);
178+
return (ThreadLocal<?>) field.get(filter);
179+
}
180+
181+
private static class CountingFilter
182+
implements IndexAwareFilter
183+
{
184+
CountingFilter(int nEffectiveness)
185+
{
186+
m_nEffectiveness = nEffectiveness;
187+
}
188+
189+
@Override
190+
public int calculateEffectiveness(Map mapIndexes, Set setKeys)
191+
{
192+
m_cCalculate.incrementAndGet();
193+
return m_nEffectiveness;
194+
}
195+
196+
@Override
197+
public Filter applyIndex(Map mapIndexes, Set setKeys)
198+
{
199+
return this;
200+
}
201+
202+
@Override
203+
public boolean evaluateEntry(Map.Entry entry)
204+
{
205+
return true;
206+
}
207+
208+
@Override
209+
public boolean evaluate(Object o)
210+
{
211+
return true;
212+
}
213+
214+
int getCalculateCount()
215+
{
216+
return m_cCalculate.get();
217+
}
218+
219+
private final AtomicInteger m_cCalculate = new AtomicInteger();
220+
private final int m_nEffectiveness;
221+
}
222+
99223
/**
100224
* Run the test with an ordered index.
101225
*/

0 commit comments

Comments
 (0)