leizhiyuan opened a new pull request, #1414:
URL: https://github.com/apache/rocketmq-clients/pull/1414

   ### Which Issue(s) This PR Fixes
   
   Fixes #1413
   
   ### Brief Description
   
   **Scenario.** With FIFO consumption, a failed message is retried locally by 
the client. Business code that branches on `MessageView::delivery_attempt()` 
(give up after N attempts, alert on the last one, ...) never triggers. The 
listener sees the server-provided attempt, typically `1`, on every local retry. 
The message is retried until `max_attempts` is exhausted, and all later 
messages of the same message group / lite topic stay blocked behind it. Both 
`PushConsumer` and `LitePushConsumer` are affected, since they share 
`FifoConsumerWorker`.
   
   The root cause is in 
`FifoConsumerWorker::consume_message_sequentially_static`. It counted attempts 
in a local variable that was never written back to the `MessageView` handed to 
the listener.
   
   This PR aligns the FIFO local retry with the Java client, 
`ProcessQueueImpl#eraseFifoMessage`:
   
   ```java
   int attempt = messageView.getDeliveryAttempt();
   if (FAILURE && attempt < maxAttempts) {
       nextAttemptDelay = retryPolicy.getNextAttemptDelay(attempt);
       attempt = messageView.incrementAndGetDeliveryAttempt();
       consume(messageView, nextAttemptDelay);       // then eraseFifoMessage 
again
   } else ... forwardToDeadLetterQueue(messageView)  // reports 
messageView.getDeliveryAttempt()
   ```
   
   - **`MessageView::increment_and_get_delivery_attempt()`** (`pub(crate)`) is 
added, matching `MessageViewImpl#incrementAndGetDeliveryAttempt`.
   - **`consume_message_sequentially_static` reads and updates the attempt on 
the `MessageView` itself.** On failure with `attempt < max_attempts`, it takes 
the delay of the current attempt, increments the attempt, sleeps, and consumes 
again. Otherwise it forwards to the DLQ with the current attempt.
   - **The standard (non-FIFO) path is unchanged.** It redelivers through the 
server, which owns the attempt.
   
   #### Behavior changes
   
   These were measured by running the new tests against both the old and the 
new code. Example: `max_attempts = 4`, a message delivered with attempt `1`, 
and a listener that always fails.
   
   | | before | after (= Java) | externally visible |
   |---|---|---|---|
   | attempts seen by the listener | `1, 1, 1, 1` | `1, 2, 3, 4` | yes (the 
fix) |
   | number of listener calls before the DLQ | 4 | 4 (unchanged) | — |
   | `delivery_attempt` in `ForwardMessageToDeadLetterQueueRequest` | `5` (max 
+ 1) | **`4`** | **yes** |
   | delay before the first local retry | `next[1]` (delay of attempt 2) | 
**`next[0]`** (delay of attempt 1) | **yes** |
   
   The last two rows change what the server receives in the DLQ request and the 
retry timing. Both now match Java. The local retry still stops at the same 
point: the message reaches the DLQ after the same number of listener calls.
   
   #### Notes for reviewers
   
   - **Missing `delivery_attempt` from the server.** If the server omits 
`delivery_attempt`, `MessageView::from_pb_message` maps it to `0`. The first 
local retry then asks the retry policy for the delay of attempt `0`, which is 
`0` with both policies. The old code incremented first, so it never asked for 
attempt `0`. Java would fail here with `checkArgument(attempt > 0)`. Servers 
always set this field on delivered messages, so no extra fallback is added. 
This keeps the behavior identical to Java for every value the server actually 
sends.
   - **Merge order and rebase.** All the PRs touching 
`rust/src/push_consumer.rs` are tracked in #1412. Checked with `git merge-tree` 
against the current PR heads:
     - #1381, #1404, #1409: merge cleanly.
     - #1407: merges cleanly as text, but the 3 tests added here then need the 
extra `filter_expression` argument of `receive_messages`.
     - #1411: conflicts in `push_consumer.rs`. Both PRs touch the listener call 
in `consume_message_sequentially_static`, and both append tests at the end of 
`mod tests`. I resolved it in a scratch worktree by keeping `consume_message()` 
from #1411 together with the attempt handling from this PR, and keeping all 
tests from both. On the result, `cargo test --lib` gives 94 passed.
   
     I suggest merging this after #1407 and #1411. I will rebase it then.
   
   ### How Did You Test This Change?
   
   **Unit tests.** Three were added. The two FIFO ones fail on `master`.
   
   - `test_fifo_local_retry_increments_delivery_attempt_until_dlq`: 
`max_attempts = 4`, and the listener always fails. It asserts that the listener 
saw `[1, 2, 3, 4]`, and that the DLQ request has `delivery_attempt == 4` and 
`max_delivery_attempts == 4`. On `master` the listener sees `[1, 1, 1, 1]` and 
the DLQ request carries `5`.
   - `test_fifo_local_retry_succeeds_with_incremented_delivery_attempt`: the 
listener fails twice and then succeeds. It asserts `[1, 2, 3]`, one ack and no 
DLQ. It also asserts the run takes < 2 s, with `next = [10ms, 10ms, 5s]`, which 
proves that the retries use the delays of attempts 1 and 2. On `master` it sees 
`[1, 1, 1]` and waits the 5 s step.
   - `test_standard_failure_keeps_server_delivery_attempt`: a regression guard 
for the non-FIFO path. A message delivered with attempt `2` is seen as `2`. It 
is nacked once via `change_invisible_duration` with the delay for attempt 2 
(`7s`), and is neither acked nor sent to the DLQ. It passes before and after.
   
   ```
   $ cargo test
   test result: ok. 92 passed; 0 failed; 1 ignored   (lib; master: 89)
   test result: ok. 3 passed; 0 failed               (lite_integration_test)
   test result: ok. 2 passed; 0 failed; 3 ignored    (doc-tests)
   # full `cargo test` repeated 10 times and `cargo test --lib` 30 times: 0 
failures
   $ cargo fmt --check                                  # clean
   $ RUSTDOCFLAGS="--cfg docsrs" cargo doc --no-deps --all-features   # no 
warnings
   $ cargo build --examples                             # ok
   ```
   
   `cargo clippy --all-features -- -D warnings` reports only the 2 `for_kv_map` 
errors that `master` already has in `rust/src/client.rs` (lines 904 and 912). 
This PR adds no clippy findings in library code.
   
   **End-to-end.** I ran this against a managed RocketMQ 5 server, with a 
`LitePushConsumer` using `fifo = true`. The listener fails while 
`delivery_attempt <= 1`. Three more messages were sent to the same lite topic 
afterwards.
   
   | | before | after |
   |---|---|---|
   | deliveries seen by the listener | `FAIL#1` × 5, the following messages 
blocked | `FAIL#1`, `FAIL#2`, `next-0#1`, `next-1#1`, `next-2#1` |
   | 30 messages on one lite topic (`fifo = true`) | in order | in order |
   
   The e2e run was done on top of #1407. Without it, `LitePushConsumer` cannot 
receive messages from this server at all.
   
   ---
   
   Tracked in #1412 (overview of the related Rust issues / PRs and a suggested 
merge order).
   


-- 
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