This is an automated email from the ASF dual-hosted git repository.

gortiz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new af39d1a6c1f Fix leaked read lock and unknown-n-gram crash in 
RealtimeNgramFilteringIndex (#19612)
af39d1a6c1f is described below

commit af39d1a6c1f34888aaeb7488f0c840e476985d12
Author: Gonzalo Ortiz Jaureguizar <[email protected]>
AuthorDate: Mon Sep 28 15:10:31 2026 +0200

    Fix leaked read lock and unknown-n-gram crash in 
RealtimeNgramFilteringIndex (#19612)
---
 .../invertedindex/RealtimeNgramFilteringIndex.java | 27 ++++++++-----
 .../RealtimeNgramFilteringIndexTest.java           | 44 ++++++++++++++++++++++
 2 files changed, 61 insertions(+), 10 deletions(-)

diff --git 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndex.java
 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndex.java
index 51373512e00..a01430932be 100644
--- 
a/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndex.java
+++ 
b/pinot-segment-local/src/main/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndex.java
@@ -109,23 +109,30 @@ public class RealtimeNgramFilteringIndex implements 
MutableTextIndex {
   /// @param searchQuery as a literal string
   @Override
   public MutableRoaringBitmap getDocIds(String searchQuery) {
-    _readLock.lock();
+    // Generated outside the read lock: it only reads the query string and the 
immutable n-gram length settings.
     Iterable<String> ngrams = generateNgrams(searchQuery);
     if (!ngrams.iterator().hasNext()) {
       return null; // No n-grams generated, return null.
     }
-    ArrayList<MutableRoaringBitmap> bitmapLst = new ArrayList<>();
+    _readLock.lock();
     try {
+      MutableRoaringBitmap resultBitmap = null;
       for (String ngram : ngrams) {
         int dictId = _ngramToDictIdMapping.getInt(ngram);
-        bitmapLst.add(_invertedIndex.getDocIds(dictId));
-      }
-      if (bitmapLst.isEmpty()) {
-        return null; // No n-grams found in the index.
-      }
-      MutableRoaringBitmap resultBitmap = bitmapLst.get(0);
-      for (int i = 1; i < bitmapLst.size(); i++) {
-        resultBitmap.and(bitmapLst.get(i));
+        if (dictId < 0) {
+          // The n-gram has never been indexed, so no document can contain all 
the n-grams of the search query.
+          return new MutableRoaringBitmap();
+        }
+        // getDocIds returns a private copy, so it is safe to intersect into 
the first one.
+        MutableRoaringBitmap docIds = _invertedIndex.getDocIds(dictId);
+        if (resultBitmap == null) {
+          resultBitmap = docIds;
+        } else {
+          resultBitmap.and(docIds);
+        }
+        if (resultBitmap.isEmpty()) {
+          return resultBitmap;
+        }
       }
       return resultBitmap;
     } finally {
diff --git 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndexTest.java
 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndexTest.java
index 2cf25954733..f221f26dc69 100644
--- 
a/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndexTest.java
+++ 
b/pinot-segment-local/src/test/java/org/apache/pinot/segment/local/realtime/impl/invertedindex/RealtimeNgramFilteringIndexTest.java
@@ -21,12 +21,16 @@ package 
org.apache.pinot.segment.local.realtime.impl.invertedindex;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import org.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import org.roaringbitmap.buffer.MutableRoaringBitmap;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
@@ -76,6 +80,46 @@ public class RealtimeNgramFilteringIndexTest {
     testSelectionResults(_ngram2To2Index, ngramQuery, null);
   }
 
+  @Test
+  public void testQueryWithNgramThatWasNeverIndexed() {
+    // "zq" never appears in the indexed documents, so nothing can match. This 
used to throw
+    // IndexOutOfBoundsException because the unknown n-gram resolved to 
dictionary id -1.
+    MutableRoaringBitmap docIds = _ngram2To3Index.getDocIds("zq");
+    assertNotNull(docIds);
+    assertTrue(docIds.isEmpty());
+
+    // A query mixing a known and an unknown n-gram must also match nothing.
+    docIds = _ngram2To3Index.getDocIds("drezq");
+    assertNotNull(docIds);
+    assertTrue(docIds.isEmpty());
+  }
+
+  @Test(timeOut = 30_000L)
+  public void testReadLockReleasedWhenNoNgramIsGenerated()
+      throws Exception {
+    RealtimeNgramFilteringIndex index = new RealtimeNgramFilteringIndex("col", 
2, 3);
+    try {
+      index.add("andrew");
+
+      // The search query is shorter than the minimum n-gram length, so no 
n-gram is generated. This used to return
+      // while still holding the read lock, which blocked every later write 
forever.
+      assertNull(index.getDocIds("r"));
+
+      CountDownLatch indexed = new CountDownLatch(1);
+      Thread writer = new Thread(() -> {
+        index.add("drew");
+        indexed.countDown();
+      });
+      writer.start();
+      assertTrue(indexed.await(10L, TimeUnit.SECONDS), "Writer thread is 
blocked by a leaked read lock");
+      writer.join();
+
+      assertEquals(index.getDocIds("drew").getCardinality(), 2);
+    } finally {
+      index.close();
+    }
+  }
+
   private List<String> getTextData() {
     return Arrays.asList("andrew", "drerew", "draw", "dr", "dr and rew", "ew 
re");
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to