From 3acf194b081478b05bb269ecc21ecc48d4b2363d Mon Sep 17 00:00:00 2001 From: Leon Finker Date: Sat, 22 Nov 2025 23:20:36 -0500 Subject: [PATCH] GEODE-10526 - IndexTrackingQueryObserver.afterIndexLookup() throws NullPointerException when indexMap ThreadLocal is uninitialized in partitioned region queries --- .../IndexTrackingQueryObserverJUnitTest.java | 56 +++++++++++++++++++ .../internal/IndexTrackingQueryObserver.java | 9 +++ 2 files changed, 65 insertions(+) diff --git a/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/IndexTrackingQueryObserverJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/IndexTrackingQueryObserverJUnitTest.java index bd7107e9c2b0..fc498dcd0c1b 100644 --- a/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/IndexTrackingQueryObserverJUnitTest.java +++ b/geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/IndexTrackingQueryObserverJUnitTest.java @@ -17,7 +17,9 @@ import static org.apache.geode.cache.Region.SEPARATOR; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.ArrayList; import java.util.Collection; import org.junit.After; @@ -163,4 +165,58 @@ public void testIndexInfoOnLocalRegion() throws Exception { assertEquals(results.size(), ((Integer) rslts).intValue()); } + /** + * Test for GEODE-10526: afterIndexLookup should handle null indexMap gracefully + * + * This test verifies that afterIndexLookup does not throw NullPointerException + * when the ThreadLocal indexMap has not been initialized. This can occur in + * partitioned region queries when afterIndexLookup is called without a + * corresponding beforeIndexLookup call, or when beforeIndexLookup fails + * before initializing the ThreadLocal. + */ + @Test + public void testAfterIndexLookupWithUninitializedThreadLocal() { + // Create a new IndexTrackingQueryObserver without initializing its ThreadLocal + IndexTrackingQueryObserver observer = new IndexTrackingQueryObserver(); + + // Create a mock result collection + Collection results = new ArrayList<>(); + results.add(new Object()); + + try { + // Call afterIndexLookup without calling beforeIndexLookup first + // This simulates the scenario where the ThreadLocal is not initialized + // Before the fix, this would throw NullPointerException at line 110 + observer.afterIndexLookup(results); + + // If we reach here, the fix is working correctly + // The method should return gracefully when indexMap is null + } catch (NullPointerException e) { + fail("GEODE-10526: afterIndexLookup should not throw NullPointerException when " + + "ThreadLocal is uninitialized. This indicates the null check is missing. " + + "Exception: " + e.getMessage()); + } + } + + /** + * Test for GEODE-10526: afterIndexLookup should handle null results parameter + * + * Verify that the existing null check for results parameter still works. + */ + @Test + public void testAfterIndexLookupWithNullResults() { + IndexTrackingQueryObserver observer = new IndexTrackingQueryObserver(); + + try { + // Call afterIndexLookup with null results + // This should return early without any exceptions + observer.afterIndexLookup(null); + + // Success - method handled null results correctly + } catch (Exception e) { + fail("afterIndexLookup should handle null results parameter gracefully. " + + "Exception: " + e.getMessage()); + } + } + } diff --git a/geode-core/src/main/java/org/apache/geode/cache/query/internal/IndexTrackingQueryObserver.java b/geode-core/src/main/java/org/apache/geode/cache/query/internal/IndexTrackingQueryObserver.java index 71b9c10e4926..a6abb50e06d5 100644 --- a/geode-core/src/main/java/org/apache/geode/cache/query/internal/IndexTrackingQueryObserver.java +++ b/geode-core/src/main/java/org/apache/geode/cache/query/internal/IndexTrackingQueryObserver.java @@ -105,6 +105,15 @@ public void afterIndexLookup(Collection results) { // append the size of the lookup results (and bucket id if its an Index on bucket) // to IndexInfo results Map. Map indexMap = (Map) indexInfo.get(); + + // Guard against uninitialized ThreadLocal in partitioned queries + if (indexMap == null) { + // beforeIndexLookup was not called or did not complete successfully. + // This can occur in partitioned region query execution across buckets + // when exceptions occur or when query execution paths bypass beforeIndexLookup. + return; + } + Index index = (Index) lastIndexUsed.get(); if (index != null) { IndexInfo indexInfo = (IndexInfo) indexMap.get(getIndexName(index, lastKeyUsed.get()));