feat: Add Dead-Letter Queue for Poison Messages - #4
Merged
Conversation
Park a payload after it exhausts maxAttempts, isolate handler throws so fan-out continues, and redrive only after a successful publish.
Publish finishes the subscriber snapshot before rethrowing the first error so later handlers still run. Retry budget and parked envelopes are keyed per wrapper, not only by message id.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A handler that always throws used to abort the rest of a fan-out. This adds a DeadLetterQueue and a withDeadLetter wrapper that retries the failed handler immediately on the same publish, then parks the payload with the source topic, attempt count, and last error.
Broker.publish isolates per-handler exceptions: every subscriber in the snapshot runs, then the first error is rethrown (so DeadLetterFullError still surfaces after siblings have been called). Retry budget and parked uniqueness are keyed per subscription, not only by message.id, so two wrappers on one queue do not steal each other's retries or hide the last error.
Redrive publishes first and only then removes the envelope, so a failed replay leaves the poison payload in place. A full DLQ throws DeadLetterFullError instead of dropping evidence.
Closes #3.