-
Notifications
You must be signed in to change notification settings - Fork 1.6k
GH-2116: Add blocking retries to RT #2124
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
Changes from all commits
7998944
85cab3f
a7e069b
29ced81
ce9656b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
package org.springframework.kafka.retrytopic; | ||
|
||
import java.time.Clock; | ||
import java.util.Arrays; | ||
import java.util.Comparator; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
|
@@ -42,7 +43,7 @@ | |
import org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter; | ||
import org.springframework.kafka.support.TopicPartitionOffset; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.backoff.FixedBackOff; | ||
import org.springframework.util.backoff.BackOff; | ||
|
||
/** | ||
* | ||
|
@@ -81,6 +82,10 @@ public class ListenerContainerFactoryConfigurer { | |
|
||
private static final long LOWEST_BACKOFF_THRESHOLD = 1500L; | ||
|
||
private BackOff providedBlockingBackOff = null; | ||
|
||
private Class<? extends Exception>[] blockingExceptionTypes = null; | ||
|
||
private Consumer<ConcurrentMessageListenerContainer<?, ?>> containerCustomizer = container -> { | ||
}; | ||
|
||
|
@@ -158,6 +163,42 @@ public KafkaListenerContainerFactory<?> decorateFactoryWithoutSettingContainerPr | |
return new RetryTopicListenerContainerFactoryDecorator(factory, configuration, false); | ||
} | ||
|
||
/** | ||
* Set a {@link BackOff} to be used with blocking retries. | ||
* If the BackOff execution returns STOP, the record will be forwarded | ||
* to the next retry topic or to the DLT, depending on how the non-blocking retries | ||
* are configured. | ||
* @param blockingBackOff the BackOff policy to be used by blocking retries. | ||
* @since 2.8.4 | ||
* @see DefaultErrorHandler | ||
*/ | ||
public void setBlockingRetriesBackOff(BackOff blockingBackOff) { | ||
Assert.notNull(blockingBackOff, "The provided BackOff cannot be null"); | ||
Assert.state(this.providedBlockingBackOff == null, () -> | ||
"Blocking retries back off has already been set. Current: " | ||
+ this.providedBlockingBackOff | ||
+ " You provided: " + blockingBackOff); | ||
this.providedBlockingBackOff = blockingBackOff; | ||
} | ||
|
||
/** | ||
* Specify the exceptions to be retried via blocking. | ||
* @param exceptionTypes the exceptions that should be retried. | ||
* @since 2.8.4 | ||
* @see DefaultErrorHandler | ||
*/ | ||
@SafeVarargs | ||
@SuppressWarnings("varargs") | ||
public final void setBlockingRetryableExceptions(Class<? extends Exception>... exceptionTypes) { | ||
Assert.notNull(exceptionTypes, "The exception types cannot be null"); | ||
Assert.noNullElements(exceptionTypes, "The exception types cannot have null elements"); | ||
Assert.state(this.blockingExceptionTypes == null, | ||
() -> "Blocking retryable exceptions have already been set." | ||
+ "Current ones: " + Arrays.toString(this.blockingExceptionTypes) | ||
+ " You provided: " + Arrays.toString(exceptionTypes)); | ||
this.blockingExceptionTypes = exceptionTypes; | ||
} | ||
|
||
private ConcurrentKafkaListenerContainerFactory<?, ?> doConfigure( | ||
ConcurrentKafkaListenerContainerFactory<?, ?> containerFactory, Configuration configuration, | ||
boolean isSetContainerProperties) { | ||
|
@@ -193,14 +234,23 @@ public void setErrorHandlerCustomizer(Consumer<CommonErrorHandler> errorHandlerC | |
|
||
protected CommonErrorHandler createErrorHandler(DeadLetterPublishingRecoverer deadLetterPublishingRecoverer, | ||
Configuration configuration) { | ||
DefaultErrorHandler errorHandler = new DefaultErrorHandler(deadLetterPublishingRecoverer, | ||
new FixedBackOff(0, 0)); | ||
DefaultErrorHandler errorHandler = createDefaultErrorHandlerInstance(deadLetterPublishingRecoverer); | ||
errorHandler.defaultFalse(); | ||
errorHandler.setCommitRecovered(true); | ||
errorHandler.setLogLevel(KafkaException.Level.DEBUG); | ||
if (this.blockingExceptionTypes != null) { | ||
errorHandler.addRetryableExceptions(this.blockingExceptionTypes); | ||
} | ||
this.errorHandlerCustomizer.accept(errorHandler); | ||
return errorHandler; | ||
} | ||
|
||
protected DefaultErrorHandler createDefaultErrorHandlerInstance(DeadLetterPublishingRecoverer deadLetterPublishingRecoverer) { | ||
return this.providedBlockingBackOff != null | ||
? new DefaultErrorHandler(deadLetterPublishingRecoverer, this.providedBlockingBackOff) | ||
: new DefaultErrorHandler(deadLetterPublishingRecoverer); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't right; the default fixed back off is (0, 9), we need (0, 0). Why not just set the field to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, considering all exceptions will return false when classified by default, it doesn't really matter what's the backoff policy - we can just leave the default one and then the user would have only to add the retryable exceptions, which would then make the default or provided back off kick in. Anyway, we can set the no ops back off too, no worries. Either way I think the other classes modifications to pass the consumer to DLPR are still needed, since we might go that route for exception classifications that return false. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh; Ok; makes sense to leave it. |
||
} | ||
|
||
protected void setupBackoffAwareMessageListenerAdapter(ConcurrentMessageListenerContainer<?, ?> container, | ||
Configuration configuration, boolean isSetContainerProperties) { | ||
AcknowledgingConsumerAwareMessageListener<?, ?> listener = checkAndCast(container.getContainerProperties() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This change is because as is if we try to remove an exception that is not there we get a NPE for trying to convert null to a boolean. With this change it'll return null and the user can deal with that as they wish.
I for one thought it would return
false
for not finding the exception in the map, but turns out it returns the current value, or null if it's not found.* <p>Returns the value to which this map previously associated the key, * or {@code null} if the map contained no mapping for the key.
It's not something extremely necessary, I can roll it back if you prefer. Or maybe it's worth adding some explanation to the javadocs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops; thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should change the javadoc to
@return the classification of the exception if the removal was successful; null otherwise.
I can do it during the merge if you like; but I have to run out for a short while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do that, no worries, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the javadoc and added two assertions to the LCFC new methods