Skip to content

DLQ publish is fire-and-forget then immediately ack'd → message-loss path #19

@iifawzi

Description

@iifawzi

Summary

DLQ publish in RunMQRetriesCheckerProcessor.moveToFinalDeadLetter is sync, fire-and-forget, with no publisher confirms — and the original message is ack'd immediately afterwards. If the broker doesn't accept the DLQ message (channel closed mid-flight, alarm state, lost route), the message is silently lost.

Where

  • src/core/consumer/processors/RunMQRetriesCheckerProcessor.ts:23-30, 48-59
  • src/core/publisher/producers/RunMQBaseProducer.ts:11-26
  • src/core/clients/RabbitMQClientChannel.ts:109-128 (no confirms)

Failure mode

  1. Handler exhausts attempts.
  2. moveToFinalDeadLetter()DLQPublisher.publish(...)basicPublish (no confirm).
  3. acknowledgeMessage(message) runs unconditionally on the next line.
  4. If the publish never lands, the original message is gone — broker has no record of it, retry queue has no record, DLQ has no record.

Proposed fix

  • Make RunMQPublisher.publish async (return Promise<void>).
  • Use a confirm-channel for publishes (or confirmSelect + waitForConfirms).
  • await the DLQ publish; only ack() on confirmed publish.
  • On confirm failure: nack(requeue=false) so the message stays in the retry/DLQ topology rather than vanishing.

Acceptance criteria

  • RunMQPublisher.publish is async (Promise<void>).
  • DLQ publish path awaits broker confirmation before ack.
  • Test: simulated broker reject after retries-exhausted does NOT lose the message.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcriticalCritical severityreleased

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions