-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DefaultAfterRollbackProcessor.isProcessInTransaction() flag should be considered in process() method #2878
Comments
Any chances that you can contribute that fix: https://github.com/spring-projects/spring-kafka/blob/main/CONTRIBUTING.adoc? |
Might try to look at this, but for that I'd have to know one thing. This is called non-transactionally from In other words, can we call That's probably a question to @garyrussell |
Currently, with a record listener, the failed record can be recovered (e.g. sent to a DLT), in which case, its offset is committed by the DARP by sending the offset to the (new) transaction (we can't mix transactional and non-transactional offset commits). Recovery is not (currently) possible with a batch listener so there is no need to start a new transaction in that case. |
@l0co, I can look at this issue (unless you are still considering sending a fix). Could you paste here the minimum required to reproduce this error from an application? Did you create your own |
@sobychacko Hello. I planned to submit something here but the end of the year is a bit hot in my company and I haven't found time yet, so feel free to provide a fix. Yes, I have my own Runnable process = () -> strategy.processor().process(consumerRecords, consumer, container, exception, recoverable, eosMode);
if (strategy.processor().isProcessInTransaction())
kafkaEventTemplate.doTransactionally(process);
else
process.run(); So, the processing is delegated to a strategy, but I have to manually wrap it in kafka transactional code. While it should work out-of-the-box. I think the fix can be adding own transactional wrapping in public void process(List<ConsumerRecord<K, V>> records, Consumer<K, V> consumer,
@Nullable MessageListenerContainer container, Exception exception, boolean recoverable, EOSMode eosMode) {
if (SeekUtils.doSeeks(((List) records), consumer, exception, recoverable,
getRecoveryStrategy((List) records, exception), container, this.logger)
&& isCommitRecovered() && this.kafkaTemplate.isTransactional()) {
// do manual transactional wrapping here --->
ConsumerRecord<K, V> skipped = records.get(0);
if (EOSMode.V1.equals(eosMode.getMode())) {
this.kafkaTemplate.sendOffsetsToTransaction(
Collections.singletonMap(new TopicPartition(skipped.topic(), skipped.partition()),
createOffsetAndMetadata(container, skipped.offset() + 1)));
}
else {
this.kafkaTemplate.sendOffsetsToTransaction(
Collections.singletonMap(new TopicPartition(skipped.topic(), skipped.partition()),
createOffsetAndMetadata(container, skipped.offset() + 1)), consumer.groupMetadata());
}
// end transactional wrapping here
}
if (!recoverable && this.backOff != null) {
try {
ListenerUtils.unrecoverableBackOff(this.backOff, this.backOffs, this.lastIntervals, container);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} And I think we are protected from non-transactional (batch) processing by |
To bo more specific: I use |
@l0co Thanks for the explanation! I see the issue but cannot reproduce it with my setup. Can you provide a simple standalone app where I can quickly reproduce it? I would like to see it failing on our end before adding the fix. Thanks! |
@sobychacko not in this year probably :( |
@l0co Any updates on the sample application? Thanks. |
I will try to prepare something this week |
Hello, that was difficult to return to this subject after longer pause, but tried to check what's going on and prepared small tester https://github.com/l0co/spring-kafka-2878-testcase. This is not a bug, though, just some issue with architecture I had with the related code. If you run if (afterRollbackProcessorToUse.isProcessInTransaction() && this.transactionTemplate != null) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
afterRollbackProcessorToUse.process(unprocessed, ListenerConsumer.this.consumer,
KafkaMessageListenerContainer.this.thisOrParentContainer, e, true,
ListenerConsumer.this.eosMode);
}
});
} We have a scenario where we call it by hand, and we had to replace it with the code above. While I think this could be just a part of the rollback processor logic, something like this: @Override
public void process(List<ConsumerRecord<K, V>> records, Consumer<K, V> consumer,
@Nullable MessageListenerContainer container, Exception exception, boolean recoverable, EOSMode eosMode) {
if (SeekUtils.doSeeks((List) records, consumer, exception, recoverable,
getFailureTracker()::recovered, container, this.logger)
&& isCommitRecovered() && this.kafkaTemplate.isTransactional()) {
// NEW CODE HERE ------------------------------------------------------>
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
ConsumerRecord<K, V> skipped = records.get(0);
this.kafkaTemplate.sendOffsetsToTransaction(
Collections.singletonMap(new TopicPartition(skipped.topic(), skipped.partition()),
createOffsetAndMetadata(container, skipped.offset() + 1)
), consumer.groupMetadata());
}
});
}
if (!recoverable && this.backOff != null) {
try {
ListenerUtils.unrecoverableBackOff(this.backOff, this.backOffs, this.lastIntervals, container);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} |
If set the transactionTemplate to DefaultAfterRolllbackProcessor, maybe first that we need ensure Such as Please correct me if there is any problem with what I say. |
Do you mean |
Yes, and |
I'm not so deeply currently in this config, but I'd just use existing |
In what version(s) of Spring for Apache Kafka are you seeing this issue?
For example:
2.8.11 (but see in the currrent master as well)
Describe the bug
In
DefaultAfterRollbackProcessor
there'sisProcessInTransaction()
flag described as:But this is not considered in
process()
method which is a part of the public interface of this class and one can expect to work. So, if you call this method yourself, in case you useisCommitRecovered()
flag plus use transactional kafka template, you will end up with "no transaction" exception. This flag is only considered by default fromorg.springframework.kafka.listener.KafkaMessageListenerContainer.ListenerConsumer#recordAfterRollback
.To Reproduce
With transactional
KafkaTemplate
setisCommitRecovered()
to true and try to executeprocess()
method yourself. This end with a "no transaction" exception.Expected behavior
The above invocation should be wrapped properly into transaction.
The text was updated successfully, but these errors were encountered: