BrighterCommand / BrighterCommand/Brighter

RocketMQ: Requeue does nothing, so the pump's delivery budget can never be exhausted

Open
#4,353 0 comments 0 reactions 1 assignee Claimed by @iancooper View on GitHub
Dominant language
C#
Stars
2.5k
Forks
296
Avg merge
1d 11h
Merged PRs (30d)
21

Description

## Summary

`RocketMessageConsumer.Requeue` is a no-op. A message whose handler keeps deferring is therefore
redelivered indefinitely and is **never dead-lettered** — the pump's delivery budget (`RequeueCount`)
cannot run down, and no `Reject` is ever issued.

Found by the FR-23 conformance behaviour (`requeue budget exhausted to DLQ`) in #4240 / #4297, which
drives a real Brighter pump. The cell stays `Deferred` on this.

## The code

`src/Paramore.Brighter.MessagingGateway.RocketMQ/RocketMessageConsumer.cs:179` — the method resolves
the `MessageView` from the bag and returns `true`. That is all of it; the one call that would act on
the broker is commented out:

```csharp
public bool Requeue(Message message, TimeSpan? delay = null)
{
if (!message.Header.Bag.TryGetValue("ReceiptHandle", out var handler) || handler is not MessageView view)
{
return false;
}

// Waiting for next RocketMQ C# version, due an issue on ChangeInvisibleDuration
// consumer.ChangeInvisibleDuration(view, TimeSpan.Zero);
return true;
}
```

So the message simply stays invisible until its lease lapses and the broker re-serves the **stored**
copy. `HandledCount` is read from the message's published properties (`ReadHandledCount`, `:422`) and
written only on send (`RocketMqMessageProducer.cs:157`), so every redelivery arrives reading the value
that was originally published. The pump bumps it to 1, `HandledCountReached(RequeueCount)` is never
true, and nothing reaches the DLQ.

## Measured, and it is not a timing problem

RocketMQ redelivers only when its 10 s invisibility lease lapses, so a budget of 3 could plausibly
outrun the conformance suite's 30 s ceiling. It does not:

| ceiling | result |
|---|---|
| 30 s (both variants) | `MT_NONE` — nothing on the DLQ |
| **150 s** (Reactor, ~15 redeliveries against a budget of 3) | **still `MT_NONE`** |

The budget does not run down slowly. It does not run down at all.

## Why this is the most complete instance of a family

This is the third transport where FR-23 has found the same root cause — **a requeue that does not
persist the delivery count cannot exhaust a Brighter-side budget**:

| transport | requeue mechanism | consequence |
|---|---|---|
| AWS SQS (#4341) | `ChangeMessageVisibility` | budget inert, but `maxReceiveCount` redrive dead-letters the message anyway |
| GCP Pub/Sub | `ModifyAckDeadline(…, 0)` | same, with `MaxDeliveryAttempts` as the redrive |
| **RocketMQ** | **nothing at all** | **no broker-side redrive is wired either, so the message is never dead-lettered by anyone** |

The transports whose FR-23 cells pass (Redis, Kafka ×3, MSSQL, Postgres, RMQ ×3) all *republish* on
requeue, carrying the updated header — which is exactly why their budgets run down.

## Suggested fix

Either restore `ChangeInvisibleDuration` once the RocketMQ C# client issue the comment refers to is
resolved *and* make the redelivered message carry an incremented delivery count, or map RocketMQ's own
delivery-attempt property into `HandledCount` on receive so the budget runs off the broker's counter.
The second needs no client fix.

## Impact

Any RocketMQ consumer whose handler defers loops for ever. Blocks 1 FR-23 conformance cell.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.