wang-jiahua opened a new issue, #10613:
URL: https://github.com/apache/rocketmq/issues/10613
**BUG REPORT**
1. Please describe the issue you observed:
The asynchronous request-reply API `DefaultMQProducer#request(Message,
RequestCallback, long)` can invoke the user `RequestCallback` **more than
once** for a single request. Two independent problems combine:
**(a) Premature `onSuccess(null)` on send success.**
In `DefaultMQProducerImpl#request(Message, RequestCallback, long)`, the
async `SendCallback#onSuccess` calls
`requestResponseFuture.executeRequestCallback()` right after the *request*
message is sent:
```java
this.sendDefaultImpl(msg, CommunicationMode.ASYNC, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
requestResponseFuture.setSendRequestOk(true);
requestResponseFuture.executeRequestCallback(); // fires
onSuccess(responseMsg==null)
}
...
```
At that moment no reply has arrived, so `responseMsg` is `null` and the user
receives `onSuccess(null)`. The other async overloads (`request(msg, selector,
arg, callback, timeout)` and `request(msg, mq, callback, timeout)`) only set
`sendRequestOk=true` here and do NOT invoke the callback, so this overload is
inconsistent.
**(b) reply-vs-timeout race → double callback.**
`ClientRemotingProcessor#processReplyMessage` (reply arrival) does a
non-atomic `get` + `remove`, while `RequestFutureHolder#scanExpiredRequest`
(timeout scan) uses `iterator.remove()`, and
`RequestResponseFuture#executeRequestCallback` has no single-shot guard. So the
reply path and the timeout path can both take ownership of the same request and
both run a callback.
As a result a single request may deliver `onSuccess(null)` then
`onSuccess(reply)`, or `onSuccess(null)` then `onException(timeout)`.
2. Please tell us about your environment:
- RocketMQ version: develop (5.x)
- OS / JDK: Linux / JDK 21
3. Steps to reproduce:
A producer issues N async `request(msg, callback, timeout)` calls; a
consumer replies via `MessageUtil.createReplyMessage(...)` after a delay near
the request timeout; count callbacks per `correlationId`.
Observed on a 4-node cluster (2000 requests, 16 threads, timeout=1000ms,
reply delay=200ms):
```
consumed=1312, replySent=1280
successNull=2000, successReply=284, exception=1716
doubleCallback=2000 (nullThenReply=284, successAndException=1716)
```
Every request received two callbacks.
4. What did you expect to see?
Each request delivers its `RequestCallback` **exactly once**:
`onSuccess(reply)` when a reply arrives, or `onException(...)` on timeout /
send failure. There should be no `onSuccess(null)` on send success.
5. What did you see instead?
Same request produced two callbacks (a premature `onSuccess(null)` followed
by the real reply or a timeout), corrupting caller state.
--
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]