Repository navigation
GH-3121: Fix NPE when acknowledging a DLQ record without an Acknowledgment header - #3309
Open
Sanskar121543 wants to merge 1 commit into
Open
Sanskar121543 wants to merge 1 commit into
Sanskar121543 wants to merge 1 commit into
Conversation
…an Acknowledgment header DlqSender#sendToDlq unconditionally called messageHeaders.get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class).acknowledge() in its finally block for MANUAL/MANUAL_IMMEDIATE ack modes. When the message being sent to the DLQ does not carry the original message's headers (e.g. an ErrorMessage without an original message, which can happen with a transactional producer + manual ack + DLQ), that lookup returns null and the binder throws a NullPointerException instead of completing the DLQ send, causing the record to be redelivered and resent to the DLQ repeatedly. Guard the lookup and log a warning instead of throwing when no Acknowledgment is available.
This branch has not been deployed
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.
What
Fixes #3121.
With a transactional producer,
ackMode=MANUAL_IMMEDIATE, andenableDlq=true, an error during processing can cause the binder to throw:instead of acknowledging the record after it's sent to the DLQ. Because the ack never happens, the record is redelivered and sent to the DLQ again, producing many duplicate DLQ entries for a single failure.
Root cause
KafkaMessageChannelBinder.handleRecordForDlqderives theMessageHeaderspassed intoDlqSender#sendToDlqfrom((ErrorMessage) message).getOriginalMessage().getHeaders(), falling back tomessage.getHeaders()when there is no original message. In the reported scenario (transactional producer + manual ack), the message handed to the DLQ error handler is anErrorMessagewith no original message, so the fallbackmessage.getHeaders()is used - and that does not carry theKafkaHeaders.ACKNOWLEDGMENTheader the original consumer record had.DlqSender#sendToDlq'sfinallyblock doesn't guard against this and unconditionally does:which NPEs as soon as that header is absent, for any
MANUAL/MANUAL_IMMEDIATEack mode record that reaches the DLQ this way.Fix
Null-check the
Acknowledgmentbefore callingacknowledge(). When it's missing, log a warning instead of throwing, so the DLQ send itself still completes cleanly rather than blowing up with an unhandled NPE. Behavior is unchanged when theAcknowledgmentheader is present (the common case) or when the ack mode isn'tMANUAL/MANUAL_IMMEDIATE.Testing
Added three unit tests to
KafkaBinderUnitTeststhat invokeKafkaMessageChannelBinder$DlqSender#sendToDlqdirectly (via reflection, following the existing pattern in that test class) with a mockedKafkaTemplate, so no broker is needed:sendToDlqDoesNotThrowWhenAcknowledgmentHeaderIsMissing- reproduces the reported NPE. I verified this test fails with the exact reportedNullPointerException(same stack frame) against the unpatched code, and passes with the fix.sendToDlqStillAcknowledgesWhenAcknowledgmentHeaderIsPresent- confirms the normal case (Acknowledgmentpresent) still callsacknowledge().sendToDlqDoesNotAcknowledgeWhenAckModeIsNotManual- confirms non-manual ack modes are unaffected.Ran
KafkaBinderUnitTests(10/10 passing) and a fullmvn test-compileof thespring-cloud-stream-binder-kafkamodule to confirm no regressions elsewhere. I did not run the broker-backed integration suite (KafkaBinderTests, embedded-Kafka tests) locally due to runtime constraints, but the change is a narrowly-scoped null guard in a singlefinallyblock that cannot change behavior on any path where theAcknowledgmentheader is present.