Skip to content

Commit b36cad4

Browse files
leonfinlfts22
andauthored
GEODE-10526 - IndexTrackingQueryObserver.afterIndexLookup() throws NullPointerException when indexMap ThreadLocal is uninitialized in partitioned region queries (#7960)
Co-authored-by: Leon Finker <[email protected]>
1 parent 490947a commit b36cad4

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/IndexTrackingQueryObserverJUnitTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import static org.apache.geode.cache.Region.SEPARATOR;
1818
import static org.junit.Assert.assertEquals;
1919
import static org.junit.Assert.assertTrue;
20+
import static org.junit.Assert.fail;
2021

22+
import java.util.ArrayList;
2123
import java.util.Collection;
2224

2325
import org.junit.After;
@@ -163,4 +165,58 @@ public void testIndexInfoOnLocalRegion() throws Exception {
163165
assertEquals(results.size(), ((Integer) rslts).intValue());
164166
}
165167

168+
/**
169+
* Test for GEODE-10526: afterIndexLookup should handle null indexMap gracefully
170+
*
171+
* This test verifies that afterIndexLookup does not throw NullPointerException
172+
* when the ThreadLocal indexMap has not been initialized. This can occur in
173+
* partitioned region queries when afterIndexLookup is called without a
174+
* corresponding beforeIndexLookup call, or when beforeIndexLookup fails
175+
* before initializing the ThreadLocal.
176+
*/
177+
@Test
178+
public void testAfterIndexLookupWithUninitializedThreadLocal() {
179+
// Create a new IndexTrackingQueryObserver without initializing its ThreadLocal
180+
IndexTrackingQueryObserver observer = new IndexTrackingQueryObserver();
181+
182+
// Create a mock result collection
183+
Collection<Object> results = new ArrayList<>();
184+
results.add(new Object());
185+
186+
try {
187+
// Call afterIndexLookup without calling beforeIndexLookup first
188+
// This simulates the scenario where the ThreadLocal is not initialized
189+
// Before the fix, this would throw NullPointerException at line 110
190+
observer.afterIndexLookup(results);
191+
192+
// If we reach here, the fix is working correctly
193+
// The method should return gracefully when indexMap is null
194+
} catch (NullPointerException e) {
195+
fail("GEODE-10526: afterIndexLookup should not throw NullPointerException when "
196+
+ "ThreadLocal is uninitialized. This indicates the null check is missing. "
197+
+ "Exception: " + e.getMessage());
198+
}
199+
}
200+
201+
/**
202+
* Test for GEODE-10526: afterIndexLookup should handle null results parameter
203+
*
204+
* Verify that the existing null check for results parameter still works.
205+
*/
206+
@Test
207+
public void testAfterIndexLookupWithNullResults() {
208+
IndexTrackingQueryObserver observer = new IndexTrackingQueryObserver();
209+
210+
try {
211+
// Call afterIndexLookup with null results
212+
// This should return early without any exceptions
213+
observer.afterIndexLookup(null);
214+
215+
// Success - method handled null results correctly
216+
} catch (Exception e) {
217+
fail("afterIndexLookup should handle null results parameter gracefully. "
218+
+ "Exception: " + e.getMessage());
219+
}
220+
}
221+
166222
}

geode-core/src/main/java/org/apache/geode/cache/query/internal/IndexTrackingQueryObserver.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ public void afterIndexLookup(Collection results) {
105105
// append the size of the lookup results (and bucket id if its an Index on bucket)
106106
// to IndexInfo results Map.
107107
Map indexMap = (Map) indexInfo.get();
108+
109+
// Guard against uninitialized ThreadLocal in partitioned queries
110+
if (indexMap == null) {
111+
// beforeIndexLookup was not called or did not complete successfully.
112+
// This can occur in partitioned region query execution across buckets
113+
// when exceptions occur or when query execution paths bypass beforeIndexLookup.
114+
return;
115+
}
116+
108117
Index index = (Index) lastIndexUsed.get();
109118
if (index != null) {
110119
IndexInfo indexInfo = (IndexInfo) indexMap.get(getIndexName(index, lastKeyUsed.get()));

0 commit comments

Comments
 (0)