xiangfu0 commented on code in PR #19982:
URL: https://github.com/apache/pinot/pull/19982#discussion_r4423019556


##########
pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java:
##########
@@ -183,6 +184,26 @@ void markRequestSent(ServerRoutingInstance 
serverRoutingInstance, int requestSen
     
_responseMap.get(serverRoutingInstance).markRequestSent(requestSentLatencyMs);
   }
 
+  // Issue #18866: Handle server DataTable deserialization errors without 
waiting for query timeout
+  void receiveDeserializationError(ServerRoutingInstance 
serverRoutingInstance, Exception exception,
+      int responseSize, int deserializationTimeMs) {
+    if (!shouldActOnServerDown(serverRoutingInstance)) {
+      return;
+    }
+    if (!_countedDownServers.add(serverRoutingInstance)) {
+      return;
+    }
+    _failedServer = serverRoutingInstance;
+    DataTable dataTable = DataTableBuilderFactory.getEmptyDataTable();
+    dataTable.getMetadata().put(DataTable.MetadataKey.REQUEST_ID.getName(), 
Long.toString(_requestId));
+    dataTable.addException(QueryErrorCode.DESERIALIZATION_ERROR.getId(),
+        exception.getMessage() != null ? exception.getMessage() : 
exception.toString());
+    ServerResponse response = _responseMap.get(serverRoutingInstance);
+    response.receiveDeserializationError(dataTable, responseSize, 
deserializationTimeMs);
+    _numServersResponded.getAndIncrement();
+    _countDownLatch.countDown();
+  }
+
   void receiveDataTable(ServerRoutingInstance serverRoutingInstance, DataTable 
dataTable, int responseSize,

Review Comment:
   **MAJOR (verified race):** `receiveDataTable` doesn't check 
`_countedDownServers`, and `ctx.close()` doesn't stop frames that were already 
decoded. `ByteToMessageDecoder` keeps firing the rest of the read batch, and 
`decodeLast` runs on inactive. So if the bytes arrive as [bad frame][good frame 
for Q2], Q2's slot for A is counted down twice, and Q2 completes without 
waiting for C. I reproduced this: `COMPLETED` in 0 ms, C=`null`, 435 
overwritten. Please guard every countdown with the same set. For example, 
return early here when `!_countedDownServers.add(server)`, and add a test for 
it.
   
   ---
   AI-generated review via IntentLab · Model: Claude Opus 5.5 (claude-opus-5-5).



##########
pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java:
##########
@@ -183,6 +184,26 @@ void markRequestSent(ServerRoutingInstance 
serverRoutingInstance, int requestSen
     
_responseMap.get(serverRoutingInstance).markRequestSent(requestSentLatencyMs);
   }
 
+  // Issue #18866: Handle server DataTable deserialization errors without 
waiting for query timeout

Review Comment:
   MINOR: please drop the `Issue #18866:` change-history prefixes here and in 
`ServerResponse`, `QueryErrorCode` and `Query.tsx`, and describe the current 
behavior instead. The `_countedDownServers` doc at L50 also needs to list the 
new path.
   
   ---
   AI-generated review via IntentLab · Model: Claude Opus 5.5 (claude-opus-5-5).



##########
pinot-core/src/main/java/org/apache/pinot/core/transport/QueryRouter.java:
##########
@@ -345,6 +345,17 @@ void receiveDataTable(ServerRoutingInstance 
serverRoutingInstance, DataTable dat
     }
   }
 
+  // Unlike receiveDataTable, a deserialization failure cannot be tied to a 
single requestId (the bytes didn't
+  // parse), so this method must broadcast to every in-flight query on the 
server. This is consistent with
+  // markServerUnavailable and safe because the channel is closed immediately 
after.
+  void markServerDeserializationError(ServerRoutingInstance 
serverRoutingInstance, Exception exception,
+      int responseSize, int deserializationTimeMs) {
+    for (AsyncQueryResponse asyncQueryResponse : 
_asyncQueryResponseMap.values()) {

Review Comment:
   **MAJOR (semantics, needs owner sign-off):** one bad response now ends 
*every* in-flight query on this channel with a 435, partial results, and a 
quarantined server. This ignores `skipUnavailableServers`. It also counts those 
servers as "responded" and suppresses `SERVER_NOT_RESPONDING`, even though most 
of them never answered. Before, one query hung. Please confirm this trade-off 
is intended and add a test that has a second in-flight query on the same 
channel.
   
   ---
   AI-generated review via IntentLab · Model: Claude Opus 5.5 (claude-opus-5-5).



##########
pinot-core/src/main/java/org/apache/pinot/core/transport/DataTableHandler.java:
##########
@@ -97,6 +97,15 @@ protected void channelRead0(ChannelHandlerContext ctx, 
ByteBuf msg) {
       LOGGER.error("Caught exception while deserializing data table of size: 
{} from server: {}", responseSize,
           _serverRoutingInstance, e);
       
_brokerMetrics.addMeteredGlobalValue(BrokerMeter.DATA_TABLE_DESERIALIZATION_EXCEPTIONS,
 1);
+      // A deserialization parse failure on malformed/corrupted response bytes 
may leave framing desynchronized or

Review Comment:
   MINOR: `LengthFieldBasedFrameDecoder` has already consumed exactly one 
frame, so a payload that fails to parse can't desync framing. Closing is needed 
because the broadcast released the latch slots of other queries, so this 
comment should say that instead. Also, this `catch` wraps `receiveDataTable` 
and the accountant updates. Consider narrowing it so post-receive failures 
don't close a healthy channel.
   
   ---
   AI-generated review via IntentLab · Model: Claude Opus 5.5 (claude-opus-5-5).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to