unbridled-41 opened a new pull request, #11050:
URL: https://github.com/apache/rocketmq/pull/11050
Closes #11049
### Problem / Evidence
The v2 gRPC protocol defines `SendMessageResponse.entries` as one
`SendResultEntry` per message of the request. The proxy sends an N-message
request to the broker as **one** remoting batch send and returns a
single-element `List<SendResult>` in both modes:
- `LocalMessageService#sendMessage`: `msgList.size() > 1` collapses into one
`MessageBatch`, one result.
- `ClusterMessageService#sendMessage`: `sendMessageAsync(..., msgList, ...)`
for N messages, one result (comma-joined per-message ids, first message's queue
offset).
`SendMessageActivity#convertToSendMessageResponse` iterates the result list
one-to-one, so an N-message request gets a **1-entry** response. Every official
v2 SDK validates the receipt count — rocketmq-clients Java `ProducerImpl#send0`
fails the send future with `InternalErrorException("[Bug] due to an unknown
reason from remote, received send receipt's quantity ... is not equal to sent
message's quantity ...")` when `sendReceipts.size() != messages.size()`. The
broker has already stored all N messages, the caller sees the send fail, and
the SDK's automatic retry stores the whole batch again — a full duplicate batch.
Deterministic regression test
`SendMessageActivityTest#testConvertToSendMessageResponseExpandsBatchSendResult`
(request with 3 messages, one `SEND_OK` batch `SendResult` with comma-joined
ids and queue offset 10). Fails on unmodified `develop`:
```
java.lang.AssertionError: one entry per request message must be returned
expected:<3> but was:<1>
at
org.apache.rocketmq.proxy.grpc.v2.producer.SendMessageActivityTest.testConvertToSendMessageResponseExpandsBatchSendResult(SendMessageActivityTest.java:212)
Tests run: 2, Failures: 1, Errors: 0, Skipped: 1 (develop @ ff8f6f74c +
test only, 2026-09-05, JDK 8)
```
### Root cause / Fix
The response conversion does not know that one `SendResult` can represent N
request messages.
Fix: `SendMessageActivity#expandBatchSendResult` expands a single batch
result into per-message results before the existing per-result conversion loop,
mirroring the client-side expansion in `ProduceAccumulator#splitSendResults` —
split the comma-joined `msgId`/`offsetMsgId`, `queueOffset + i` per entry,
preserve `transactionId`/`regionId`/`recallHandle`, and reuse the batch result
for every message when the broker returned no per-message ids (inner-batch
message responses). The request is available at the conversion site, so local
and cluster mode are fixed at once.
### Priority
PRIORITY = 76(影响 32 + 波及范围 14 + 可复现 16 + 维护价值 14),FIX_CONFIDENCE = 82。
- 影响 32/40: batch sends through the proxy fail in the SDK although the
broker persisted the batch; automatic retries duplicate the entire batch —
user-visible data correctness issue.
- 波及范围 14/20: all v2 SDKs (Java/Go/C++/C#) sending `SendMessageRequest` with
more than one message through the proxy; the client-side precedent
(`ProduceAccumulator`) shows batch sends are a real, used path.
- 可复现 16/20: deterministic unit test at the conversion boundary; the
end-to-end receipt-count check in the SDKs is a hard failure.
- 维护价值 14/20: restores the documented protocol contract (`entries`
one-to-one with `messages`); the expansion algorithm is already established in
the client module.
### Tests
- Regression test added:
`SendMessageActivityTest#testConvertToSendMessageResponseExpandsBatchSendResult`
(fails before the fix as shown above).
- After the fix (`develop @ ff8f6f74c` + this change, 2026-09-05, JDK 8):
```
mvn -pl proxy test -Dtest=SendMessageActivityTest
Tests run: 13, Failures: 0, Errors: 0, Skipped: 0 — BUILD SUCCESS (grpc
v2: 12, remoting activity: 1)
mvn -pl proxy test
Tests run: 318, Failures: 0, Errors: 0, Skipped: 3 — BUILD SUCCESS
```
Note: this test cannot run under a local JDK 21 (pre-existing environmental
baseline: Mockito/ByteBuddy "Unsupported class file major version 65" affects
the whole test class before any change); the CI matrix builds and tests with
JDK 8, where all runs above were performed.
### Risk
Low. The expansion only triggers when the result list holds exactly one
result while the request carries at least two messages — a shape that today
always comes from the batch send path; single-message sends and
already-per-message result lists pass through untouched. Per-message offsets
are derived exactly as the remoting client does (`queueOffset + i`), and the
fallback for id-less batch results matches
`ProduceAccumulator#splitSendResults` semantics, so v2 SDK receipt processing
stays consistent with the remoting client behavior.
--
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]