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 badcdc0d131 Check for query termination before each transform 
evaluation (#19907)
badcdc0d131 is described below

commit badcdc0d131b6e4f8a2097d74ee4b4122b6c6198
Author: Gonzalo Ortiz Jaureguizar <[email protected]>
AuthorDate: Wed Nov 4 10:01:37 2026 +0100

    Check for query termination before each transform evaluation (#19907)
---
 .../operator/docvalsets/TransformBlockValSet.java  |  29 ++++
 .../TransformBlockValSetTerminationTest.java       | 115 +++++++++++++++
 .../TransformEvaluationDeadlineQueriesTest.java    | 160 +++++++++++++++++++++
 3 files changed, 304 insertions(+)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java
index e7fd11a8bf9..5143126fd6f 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java
@@ -19,6 +19,7 @@
 package org.apache.pinot.core.operator.docvalsets;
 
 import java.math.BigDecimal;
+import java.util.function.Supplier;
 import javax.annotation.Nullable;
 import org.apache.pinot.core.common.BlockValSet;
 import org.apache.pinot.core.operator.blocks.ValueBlock;
@@ -27,6 +28,7 @@ import 
org.apache.pinot.core.operator.transform.function.TransformFunction;
 import org.apache.pinot.core.plan.DocIdSetPlanNode;
 import org.apache.pinot.segment.spi.index.reader.Dictionary;
 import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.query.QueryThreadContext;
 import org.apache.pinot.spi.trace.InvocationRecording;
 import org.apache.pinot.spi.trace.InvocationScope;
 import org.apache.pinot.spi.trace.Tracing;
@@ -38,9 +40,14 @@ import org.roaringbitmap.RoaringBitmap;
 /// block.
 ///
 /// Caller is responsible for calling the correct method based on the data 
source metadata for the block value set.
+///
+/// The values are not cached: each `get*Values*()` call evaluates the 
transform function on the whole block again. To
+/// make sure that a caller that evaluates the transform many times in one 
block (for example, once per row) cannot run
+/// past the query deadline, each evaluation first checks whether the query is 
terminated.
 public class TransformBlockValSet implements BlockValSet {
   private final ValueBlock _valueBlock;
   private final TransformFunction _transformFunction;
+  private final Supplier<String> _terminationScope;
 
   private boolean _nullBitmapSet;
   private RoaringBitmap _nullBitmap;
@@ -50,6 +57,7 @@ public class TransformBlockValSet implements BlockValSet {
   public TransformBlockValSet(ValueBlock valueBlock, TransformFunction 
transformFunction) {
     _valueBlock = valueBlock;
     _transformFunction = transformFunction;
+    _terminationScope = () -> "transform function: " + 
transformFunction.getName();
   }
 
   @Nullable
@@ -89,6 +97,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public int[] getDictionaryIdsSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.INT, true);
       return _transformFunction.transformToDictIdsSV(_valueBlock);
@@ -97,6 +106,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public int[] getIntValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.INT, true);
       return _transformFunction.transformToIntValuesSV(_valueBlock);
@@ -105,6 +115,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public long[] getLongValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.LONG, true);
       return _transformFunction.transformToLongValuesSV(_valueBlock);
@@ -113,6 +124,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public float[] getFloatValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.FLOAT, true);
       return _transformFunction.transformToFloatValuesSV(_valueBlock);
@@ -121,6 +133,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public double[] getDoubleValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.DOUBLE, true);
       return _transformFunction.transformToDoubleValuesSV(_valueBlock);
@@ -129,6 +142,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public BigDecimal[] getBigDecimalValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.BIG_DECIMAL, true);
       return _transformFunction.transformToBigDecimalValuesSV(_valueBlock);
@@ -137,6 +151,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public String[] getStringValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.STRING, true);
       return _transformFunction.transformToStringValuesSV(_valueBlock);
@@ -145,6 +160,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public byte[][] getBytesValuesSV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.BYTES, true);
       return _transformFunction.transformToBytesValuesSV(_valueBlock);
@@ -186,6 +202,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public int[][] getDictionaryIdsMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.INT, false);
       return _transformFunction.transformToDictIdsMV(_valueBlock);
@@ -194,6 +211,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public int[][] getIntValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.INT, false);
       return _transformFunction.transformToIntValuesMV(_valueBlock);
@@ -202,6 +220,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public long[][] getLongValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.LONG, false);
       return _transformFunction.transformToLongValuesMV(_valueBlock);
@@ -210,6 +229,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public float[][] getFloatValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.FLOAT, false);
       return _transformFunction.transformToFloatValuesMV(_valueBlock);
@@ -218,6 +238,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public double[][] getDoubleValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.DOUBLE, false);
       return _transformFunction.transformToDoubleValuesMV(_valueBlock);
@@ -226,6 +247,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public BigDecimal[][] getBigDecimalValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.BIG_DECIMAL, false);
       return _transformFunction.transformToBigDecimalValuesMV(_valueBlock);
@@ -234,6 +256,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public String[][] getStringValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.STRING, false);
       return _transformFunction.transformToStringValuesMV(_valueBlock);
@@ -242,6 +265,7 @@ public class TransformBlockValSet implements BlockValSet {
 
   @Override
   public byte[][][] getBytesValuesMV() {
+    checkTermination();
     try (InvocationScope scope = 
Tracing.getTracer().createScope(TransformBlockValSet.class)) {
       recordTransformValues(scope, DataType.BYTES, false);
       return _transformFunction.transformToBytesValuesMV(_valueBlock);
@@ -311,6 +335,11 @@ public class TransformBlockValSet implements BlockValSet {
     }
   }
 
+  /// Throws if the query is terminated or timed out. Called before each 
evaluation of the transform function.
+  private void checkTermination() {
+    QueryThreadContext.checkTermination(_terminationScope);
+  }
+
   private void recordTransformValues(InvocationRecording recording, DataType 
dataType, boolean singleValue) {
     if (recording.isEnabled()) {
       int numDocs = _valueBlock.getNumDocs();
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSetTerminationTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSetTerminationTest.java
new file mode 100644
index 00000000000..94389522e30
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSetTerminationTest.java
@@ -0,0 +1,115 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.operator.docvalsets;
+
+import org.apache.pinot.core.operator.blocks.ValueBlock;
+import org.apache.pinot.core.operator.transform.function.TransformFunction;
+import org.apache.pinot.spi.accounting.ThreadAccountantUtils;
+import org.apache.pinot.spi.exception.EarlyTerminationException;
+import org.apache.pinot.spi.exception.QueryErrorCode;
+import org.apache.pinot.spi.exception.QueryException;
+import org.apache.pinot.spi.exception.TerminationException;
+import org.apache.pinot.spi.query.QueryExecutionContext;
+import org.apache.pinot.spi.query.QueryThreadContext;
+import org.testng.annotations.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+
+/// Checks that [TransformBlockValSet] does not evaluate the transform 
function once the query is terminated, timed
+/// out or interrupted.
+public class TransformBlockValSetTerminationTest {
+  private static final String[] VALUES = {"a", "b"};
+
+  @Test
+  public void testEvaluatesWhenQueryIsActive() {
+    TransformFunction transformFunction = mockTransformFunction();
+    try (QueryThreadContext ignore = QueryThreadContext.openForSseTest()) {
+      assertSame(newValSet(transformFunction).getStringValuesSV(), VALUES);
+    }
+  }
+
+  @Test
+  public void testEvaluatesWithoutQueryThreadContext() {
+    TransformFunction transformFunction = mockTransformFunction();
+    assertSame(newValSet(transformFunction).getStringValuesSV(), VALUES);
+  }
+
+  @Test
+  public void testStopsWhenQueryIsTerminated() {
+    TransformFunction transformFunction = mockTransformFunction();
+    QueryExecutionContext executionContext = 
QueryExecutionContext.forSseTest();
+    try (QueryThreadContext ignore = QueryThreadContext.open(executionContext,
+        ThreadAccountantUtils.getNoOpAccountant())) {
+      executionContext.terminate(QueryErrorCode.QUERY_CANCELLATION, 
"terminated for test");
+      TerminationException e =
+          expectThrows(TerminationException.class, () -> 
newValSet(transformFunction).getStringValuesSV());
+      assertEquals(e.getErrorCode(), QueryErrorCode.QUERY_CANCELLATION);
+    }
+    verify(transformFunction, never()).transformToStringValuesSV(any());
+  }
+
+  @Test
+  public void testStopsWhenQueryIsTimedOut() {
+    TransformFunction transformFunction = mockTransformFunction();
+    long nowMs = System.currentTimeMillis();
+    QueryExecutionContext executionContext = new 
QueryExecutionContext(QueryExecutionContext.QueryType.SSE, 123L,
+        "cid", "default", nowMs - 1000, nowMs - 1, nowMs - 1, "brokerId", 
"instanceId", "");
+    try (QueryThreadContext ignore = QueryThreadContext.open(executionContext,
+        ThreadAccountantUtils.getNoOpAccountant())) {
+      QueryException e = expectThrows(QueryException.class, () -> 
newValSet(transformFunction).getStringValuesSV());
+      assertEquals(e.getErrorCode(), QueryErrorCode.EXECUTION_TIMEOUT);
+    }
+    verify(transformFunction, never()).transformToStringValuesSV(any());
+  }
+
+  @Test
+  public void testStopsWhenThreadIsInterrupted() {
+    TransformFunction transformFunction = mockTransformFunction();
+    try (QueryThreadContext ignore = QueryThreadContext.openForSseTest()) {
+      Thread.currentThread().interrupt();
+      assertThrows(EarlyTerminationException.class, () -> 
newValSet(transformFunction).getStringValuesSV());
+    } finally {
+      // Clear the flag in case the check did not consume it.
+      Thread.interrupted();
+    }
+    verify(transformFunction, never()).transformToStringValuesSV(any());
+  }
+
+  private static TransformFunction mockTransformFunction() {
+    TransformFunction transformFunction = mock(TransformFunction.class);
+    when(transformFunction.getName()).thenReturn("testTransform");
+    
when(transformFunction.transformToStringValuesSV(any())).thenReturn(VALUES);
+    return transformFunction;
+  }
+
+  private static TransformBlockValSet newValSet(TransformFunction 
transformFunction) {
+    ValueBlock valueBlock = mock(ValueBlock.class);
+    when(valueBlock.getNumDocs()).thenReturn(VALUES.length);
+    return new TransformBlockValSet(valueBlock, transformFunction);
+  }
+}
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/queries/TransformEvaluationDeadlineQueriesTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/queries/TransformEvaluationDeadlineQueriesTest.java
new file mode 100644
index 00000000000..a29eff1350e
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/queries/TransformEvaluationDeadlineQueriesTest.java
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.queries;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import org.apache.pinot.common.function.FunctionUtils;
+import org.apache.pinot.core.operator.BaseOperator;
+import 
org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader;
+import 
org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl;
+import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader;
+import org.apache.pinot.segment.spi.ImmutableSegment;
+import org.apache.pinot.segment.spi.IndexSegment;
+import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig;
+import org.apache.pinot.spi.accounting.ThreadAccountantUtils;
+import org.apache.pinot.spi.config.table.TableConfig;
+import org.apache.pinot.spi.config.table.TableType;
+import org.apache.pinot.spi.data.FieldSpec.DataType;
+import org.apache.pinot.spi.data.Schema;
+import org.apache.pinot.spi.data.readers.GenericRow;
+import org.apache.pinot.spi.exception.QueryErrorCode;
+import org.apache.pinot.spi.exception.QueryException;
+import org.apache.pinot.spi.query.QueryExecutionContext;
+import org.apache.pinot.spi.query.QueryThreadContext;
+import org.apache.pinot.spi.utils.ReadMode;
+import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
+import org.testng.SkipException;
+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.assertTrue;
+import static org.testng.Assert.expectThrows;
+
+
+/// Checks that the query deadline stops the group-by processing of a block 
between two transform evaluations.
+///
+/// The segment has one block. `DefaultGroupByExecutor.process()` handles the 
whole block in one call. Each aggregation
+/// evaluates `sleep(col) + k` on the whole block. `col` is `SLEEP_MS` in 
every row, so one evaluation takes
+/// `NUM_RECORDS * SLEEP_MS` = 200 ms, and the 10 aggregations take 2 s in 
total. No termination check runs inside one
+/// block, except the check that `TransformBlockValSet` runs before each 
transform evaluation. So with a 500 ms
+/// deadline, the query must fail with a timeout from inside the block after 
about 600 ms. Without that check, the
+/// block runs to the end (2 s), and the timeout only comes from the 
`nextBlock()` check at the next block boundary.
+public class TransformEvaluationDeadlineQueriesTest extends BaseQueriesTest {
+  private static final File INDEX_DIR =
+      new File(FileUtils.getTempDirectory(), 
"TransformEvaluationDeadlineQueriesTest");
+  private static final String RAW_TABLE_NAME = "testTable";
+  private static final String SEGMENT_NAME = "testSegment";
+  private static final String COLUMN = "col";
+  private static final int NUM_RECORDS = 20;
+  private static final int SLEEP_MS = 10;
+  private static final int NUM_AGGREGATIONS = 10;
+  // The first transform evaluation must start before the deadline. The margin 
covers class loading and GC pauses on
+  // slow CI runners.
+  private static final long DEADLINE_MS = 500;
+  // The full block takes NUM_AGGREGATIONS * NUM_RECORDS * SLEEP_MS = 2 s. 
Stopping at the deadline plus at most one
+  // more evaluation (200 ms) is well below this limit.
+  private static final long MAX_ELAPSED_MS = 1500;
+
+  private static final Schema SCHEMA = new 
Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME)
+      .addSingleValueDimension(COLUMN, DataType.LONG)
+      .build();
+  private static final TableConfig TABLE_CONFIG =
+      new 
TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build();
+
+  private IndexSegment _indexSegment;
+
+  @Override
+  protected String getFilter() {
+    return "";
+  }
+
+  @Override
+  protected IndexSegment getIndexSegment() {
+    return _indexSegment;
+  }
+
+  @Override
+  protected List<IndexSegment> getIndexSegments() {
+    return List.of(_indexSegment);
+  }
+
+  @BeforeClass
+  public void setUp()
+      throws Exception {
+    FileUtils.deleteQuietly(INDEX_DIR);
+    List<GenericRow> records = new ArrayList<>(NUM_RECORDS);
+    for (int i = 0; i < NUM_RECORDS; i++) {
+      GenericRow record = new GenericRow();
+      // Every row sleeps SLEEP_MS: the aggregations call sleep(col).
+      record.putValue(COLUMN, (long) SLEEP_MS);
+      records.add(record);
+    }
+    SegmentGeneratorConfig segmentGeneratorConfig = new 
SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA);
+    segmentGeneratorConfig.setTableName(RAW_TABLE_NAME);
+    segmentGeneratorConfig.setSegmentName(SEGMENT_NAME);
+    segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath());
+    SegmentIndexCreationDriverImpl driver = new 
SegmentIndexCreationDriverImpl();
+    driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records));
+    driver.build();
+    ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new 
File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap);
+    _indexSegment = immutableSegment;
+  }
+
+  @Test
+  public void testDeadlineStopsBlockBetweenTransformEvaluations() {
+    if (!FunctionUtils.isAssertEnabled()) {
+      throw new SkipException("sleep() only sleeps when assertions are 
enabled");
+    }
+    StringBuilder query = new StringBuilder("SELECT col");
+    for (int k = 0; k < NUM_AGGREGATIONS; k++) {
+      // A different constant per aggregation, so that each one is a different 
transform expression. The constant is
+      // outside sleep(), so that every evaluation sleeps the same time.
+      query.append(", SUM(sleep(col) + ").append(k).append(")");
+    }
+    query.append(" FROM testTable GROUP BY col");
+
+    // Plan before the deadline starts: planning the first query of the JVM 
can take longer than the deadline.
+    BaseOperator<?> operator = getOperator(query.toString());
+    long startMs = System.currentTimeMillis();
+    QueryExecutionContext executionContext =
+        new QueryExecutionContext(QueryExecutionContext.QueryType.SSE, 123L, 
"cid", "default", startMs,
+            startMs + DEADLINE_MS, startMs + DEADLINE_MS, "brokerId", 
"instanceId", "");
+    try (QueryThreadContext ignore = QueryThreadContext.open(executionContext,
+        ThreadAccountantUtils.getNoOpAccountant())) {
+      QueryException e = expectThrows(QueryException.class, 
operator::nextBlock);
+      // The block must start before the deadline, so that the timeout comes 
from inside the block.
+      assertTrue(e.getMessage().contains("transform function"), 
e.getMessage());
+      assertEquals(e.getErrorCode(), QueryErrorCode.EXECUTION_TIMEOUT);
+    }
+    long elapsedMs = System.currentTimeMillis() - startMs;
+    assertTrue(elapsedMs < MAX_ELAPSED_MS,
+        "The query must stop soon after the deadline (" + DEADLINE_MS + " ms), 
but it ran " + elapsedMs + " ms");
+  }
+
+  @AfterClass
+  public void tearDown() {
+    _indexSegment.destroy();
+    FileUtils.deleteQuietly(INDEX_DIR);
+  }
+}


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

Reply via email to