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]

Reply via email to