Skip to content

KafkaMessageListenerContainer doesn't properly clear threadState of AfterRollbackProcessor #3076

@rroesch1

Description

@rroesch1

In what version(s) of Spring for Apache Kafka are you seeing this issue?

3.1.1

Describe the bug

The KafkaMessageListenerContainer doesn't clear the threadState of AfterRollbackProcessor after a successfull processing attempt if transactionManager is set.

To Reproduce

  • I have a KafkaMessageListenerContainer configured with a BatchMessageListener and KafkaTransactionManager (batched exactly-once processing)
  • Additionally i set a DefaultAfterRollbackProcessor with ExponentialBackOff
  • i do not set any CommonErrorHandler (AbstractMessageListenerContainer#setCommonErrorHandler)

Example snippet:

    @Bean
    MessageListenerContainer messageListenerContainer(
            KafkaTemplate<byte[], byte[]> kafkaTemplate,
            ConsumerFactory<byte[], byte[]> consumerFactory,
            KafkaTransactionManager<byte[], byte[]> transactionManager) {

        // initialize MyEventListener which is a BatchMessageListener
        var myEventListener = new MyEventListener(kafkaTemplate);
        // initialize container properties
        var props = new ContainerProperties(sourceTopics);
        props.setMessageListener(myEventListener);
        props.setTransactionManager(transactionManager); // enable transaction manager for exactly-once processing
        // initialize back-off strategy for retries
        var backOff = new ExponentialBackOff(1000, 2);
        backOff.setMaxInterval(300000L); // set back-off limit to 5min
        var afterRollbackProcessor = new DefaultAfterRollbackProcessor<>(backOff);
        // initialize MessageListenerContainer
        var container = new KafkaMessageListenerContainer<>(consumerFactory, props);
        container.setAfterRollbackProcessor(afterRollbackProcessor);
        return container;
    }

(Un-)expected behavior

  • KafkaMessageListenerContainer calls the AfterRollbackProcessor after each failed transacation (as expected)
  • the DefaultAfterRollbackProcessor uses the configured BackOff after each failed transacttion (as expected)
  • after a sucessfull processing attempt, AfterRollbackProcessor#clearThreadState is not being called (that's unexpected to me)
  • thus the state of the BackOffExecution in DefaultAfterRollbackProcessor (which is "some kind of ThreadLocal state") is never reset (that's unexpected to me)
  • thus, on subsequent transaction rollbacks, DefaultAfterRollbackProcessor reuses the previously used BackOffExecution (that's unexpected to me)
  • this unexpected behavior only appears if commonErrorHandler is set to null (that's the default if transactionManager is set)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions