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

xiangfu0 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 66b7f21344a Preserve query error codes during mailbox cancellation 
(#19682)
66b7f21344a is described below

commit 66b7f21344abb25f1b5cc1e9edff002343ccd648
Author: Xiang Fu <[email protected]>
AuthorDate: Mon Sep 28 16:13:12 2026 +0700

    Preserve query error codes during mailbox cancellation (#19682)
    
    Reproduce: Run QueryKillingIntegrationTest.testCpuBasedServerQueryKilling 
with MSE; the broker can report 503 for a server CPU kill that should be 245.
---
 .../pinot/query/mailbox/GrpcSendingMailbox.java    | 14 +++++---
 .../query/mailbox/InMemorySendingMailbox.java      | 13 ++++---
 .../query/mailbox/GrpcSendingMailboxTest.java      | 42 ++++++++++++++++++++++
 .../query/mailbox/InMemorySendingMailboxTest.java  | 38 ++++++++++++++++++++
 4 files changed, 98 insertions(+), 9 deletions(-)

diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java
index 90ae69c8d0e..74eabb3219a 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/GrpcSendingMailbox.java
@@ -134,8 +134,8 @@ public class GrpcSendingMailbox implements SendingMailbox {
     // and must always reach the receiver, so they bypass the back-pressure 
gate. Bypassing also disables the
     // cooperative termination poll inside [#awaitReady]; without it, a 
terminate signal raised while the sender is
     // mid-way through pushing an error EOS would unwind [#sendInternal] with 
a TerminationException, leave
-    // [#_senderSideClosed] false, and let [#cancel] run and overwrite the 
original error code with
-    // QUERY_CANCELLATION on the receiver side.
+    // [#_senderSideClosed] false, and let [#cancel] run and replace the 
original error code with
+    // QUERY_CANCELLATION if its throwable does not carry a QueryErrorCode.
     //
     // Race against a concurrent [#cancel]: both paths perform an early 
`isTerminated()` check before taking
     // [#_readyLock], so two threads can both pass that check and enter their 
respective lock sections. The cancel
@@ -294,10 +294,14 @@ public class GrpcSendingMailbox implements SendingMailbox 
{
     // sendContent()'s acquisition nests safely.
     _readyLock.lock();
     try {
-      String msg = t != null ? t.getMessage() : "Unknown";
+      QueryErrorCode errorCode = QueryErrorCode.fromThrowable(t, 
QueryErrorCode.QUERY_CANCELLATION);
+      String msg = t != null ? t.getMessage() : null;
+      if (msg == null) {
+        msg = "Unknown";
+      }
       // NOTE: DO NOT use onError() because it will terminate the stream, and 
receiver might not get the callback
-      MseBlock errorBlock = ErrorMseBlock.fromError(
-          QueryErrorCode.QUERY_CANCELLATION, "Cancelled by sender with 
exception: " + msg);
+      MseBlock errorBlock = ErrorMseBlock.fromError(errorCode,
+          errorCode == QueryErrorCode.QUERY_CANCELLATION ? "Cancelled by 
sender with exception: " + msg : msg);
       processAndSend(errorBlock, List.of(), /* bypassReady */ true);
       _contentObserver.onCompleted();
     } catch (Exception e) {
diff --git 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java
 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java
index c9cef3552d3..72a737ac89f 100644
--- 
a/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java
+++ 
b/pinot-query-runtime/src/main/java/org/apache/pinot/query/mailbox/InMemorySendingMailbox.java
@@ -24,7 +24,7 @@ import org.apache.pinot.query.runtime.blocks.ErrorMseBlock;
 import org.apache.pinot.query.runtime.blocks.MseBlock;
 import org.apache.pinot.query.runtime.operator.MailboxSendOperator;
 import org.apache.pinot.segment.spi.memory.DataBuffer;
-import org.apache.pinot.spi.exception.QueryCancelledException;
+import org.apache.pinot.spi.exception.QueryErrorCode;
 import org.apache.pinot.spi.query.QueryThreadContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -119,9 +119,14 @@ public class InMemorySendingMailbox implements 
SendingMailbox {
     if (_receivingMailbox == null) {
       _receivingMailbox = _mailboxService.getReceivingMailbox(_id);
     }
-    _receivingMailbox.setErrorBlock(
-        ErrorMseBlock.fromException(new QueryCancelledException(
-            "Cancelled by sender with exception: " + t.getMessage())), 
List.of());
+    QueryErrorCode errorCode = QueryErrorCode.fromThrowable(t, 
QueryErrorCode.QUERY_CANCELLATION);
+    String msg = t != null ? t.getMessage() : null;
+    if (msg == null) {
+      msg = "Unknown";
+    }
+    _receivingMailbox.setErrorBlock(ErrorMseBlock.fromError(errorCode,
+        errorCode == QueryErrorCode.QUERY_CANCELLATION ? "Cancelled by sender 
with exception: " + msg : msg),
+        List.of());
   }
 
   @Override
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/GrpcSendingMailboxTest.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/GrpcSendingMailboxTest.java
index 9241f45b754..bda3ea2a8e8 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/GrpcSendingMailboxTest.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/GrpcSendingMailboxTest.java
@@ -26,6 +26,7 @@ import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.CyclicBarrier;
 import java.util.concurrent.ExecutorService;
@@ -50,19 +51,60 @@ import 
org.apache.pinot.segment.spi.memory.CompoundDataBuffer;
 import org.apache.pinot.segment.spi.memory.DataBuffer;
 import org.apache.pinot.segment.spi.memory.PinotByteBuffer;
 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.QueryThreadContext;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
 
 public class GrpcSendingMailboxTest {
 
+  @Test(dataProvider = "cancellationErrors")
+  @SuppressWarnings("unchecked")
+  public void cancelPreservesQueryErrorCode(Exception exception, 
QueryErrorCode expectedCode, String expectedMessage)
+      throws IOException {
+    ClientCallStreamObserver<MailboxContent> observer = 
mock(ClientCallStreamObserver.class);
+    GrpcSendingMailbox mailbox = new GrpcSendingMailbox("test-mailbox", 
mock(ChannelManager.class), "localhost", 0,
+        Long.MAX_VALUE, new StatMap<>(MailboxSendOperator.StatKey.class), 4 * 
1024 * 1024, false) {
+      @Override
+      ClientCallStreamObserver<MailboxContent> getContentObserver() {
+        return observer;
+      }
+    };
+
+    mailbox.cancel(exception);
+
+    ArgumentCaptor<MailboxContent> content = 
ArgumentCaptor.forClass(MailboxContent.class);
+    verify(observer).onNext(content.capture());
+    verify(observer).onCompleted();
+    DataBlock errorBlock = 
DataBlockUtils.deserialize(List.of(content.getValue().getPayload().asReadOnlyByteBuffer()));
+    assertEquals(errorBlock.getExceptions(), Map.of(expectedCode.getId(), 
expectedMessage));
+  }
+
+  @DataProvider(name = "cancellationErrors")
+  public Object[][] cancellationErrors() {
+    return new Object[][]{
+        {new QueryException(QueryErrorCode.SERVER_RESOURCE_LIMIT_EXCEEDED, 
"CPU limit"),
+            QueryErrorCode.SERVER_RESOURCE_LIMIT_EXCEEDED, "CPU limit"},
+        {new TerminationException(QueryErrorCode.EXECUTION_TIMEOUT, "execution 
timed out"),
+            QueryErrorCode.EXECUTION_TIMEOUT, "execution timed out"},
+        {new QueryException(QueryErrorCode.EXECUTION_TIMEOUT, (String) null),
+            QueryErrorCode.EXECUTION_TIMEOUT, "Unknown"},
+        {new RuntimeException("ordinary cancellation"), 
QueryErrorCode.QUERY_CANCELLATION,
+            "Cancelled by sender with exception: ordinary cancellation"},
+        {null, QueryErrorCode.QUERY_CANCELLATION, "Cancelled by sender with 
exception: Unknown"}
+    };
+  }
+
   @Test
   public void sendDataThrowsWhenQueryTerminated() {
     ChannelManager channelManager = Mockito.mock(ChannelManager.class);
diff --git 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/InMemorySendingMailboxTest.java
 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/InMemorySendingMailboxTest.java
index c550d37af5b..543c86a146f 100644
--- 
a/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/InMemorySendingMailboxTest.java
+++ 
b/pinot-query-runtime/src/test/java/org/apache/pinot/query/mailbox/InMemorySendingMailboxTest.java
@@ -19,20 +19,58 @@
 package org.apache.pinot.query.mailbox;
 
 import java.util.List;
+import java.util.Map;
 import org.apache.pinot.common.datatable.StatMap;
 import org.apache.pinot.common.utils.DataSchema;
+import org.apache.pinot.query.runtime.blocks.ErrorMseBlock;
 import org.apache.pinot.query.runtime.blocks.RowHeapDataBlock;
 import org.apache.pinot.query.runtime.operator.MailboxSendOperator;
 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.QueryThreadContext;
 import org.mockito.Mockito;
 import org.testng.Assert;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+
 
 public class InMemorySendingMailboxTest {
 
+  @Test(dataProvider = "cancellationErrors")
+  public void cancelPreservesQueryErrorCode(Exception exception, 
QueryErrorCode expectedCode, String expectedMessage) {
+    ReceivingMailbox receivingMailbox = new ReceivingMailbox("test-mailbox", 
1);
+    receivingMailbox.registeredReader(mock(ReceivingMailbox.Reader.class));
+    MailboxService mailboxService = mock(MailboxService.class);
+    
when(mailboxService.getReceivingMailbox("test-mailbox")).thenReturn(receivingMailbox);
+    InMemorySendingMailbox mailbox = new 
InMemorySendingMailbox("test-mailbox", mailboxService, Long.MAX_VALUE,
+        new StatMap<>(MailboxSendOperator.StatKey.class));
+
+    mailbox.cancel(exception);
+
+    ErrorMseBlock errorBlock = (ErrorMseBlock) 
receivingMailbox.poll().getBlock();
+    assertEquals(errorBlock.getErrorMessages(), Map.of(expectedCode, 
expectedMessage));
+  }
+
+  @DataProvider(name = "cancellationErrors")
+  public Object[][] cancellationErrors() {
+    return new Object[][]{
+        {new QueryException(QueryErrorCode.SERVER_RESOURCE_LIMIT_EXCEEDED, 
"CPU limit"),
+            QueryErrorCode.SERVER_RESOURCE_LIMIT_EXCEEDED, "CPU limit"},
+        {new TerminationException(QueryErrorCode.EXECUTION_TIMEOUT, "execution 
timed out"),
+            QueryErrorCode.EXECUTION_TIMEOUT, "execution timed out"},
+        {new QueryException(QueryErrorCode.EXECUTION_TIMEOUT, (String) null),
+            QueryErrorCode.EXECUTION_TIMEOUT, "Unknown"},
+        {new RuntimeException("ordinary cancellation"), 
QueryErrorCode.QUERY_CANCELLATION,
+            "Cancelled by sender with exception: ordinary cancellation"},
+        {null, QueryErrorCode.QUERY_CANCELLATION, "Cancelled by sender with 
exception: Unknown"}
+    };
+  }
+
   @Test
   public void sendDataThrowsWhenQueryTerminated() {
     MailboxService mailboxService = Mockito.mock(MailboxService.class);


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

Reply via email to