GH-8770: Add PostgresSubsChannel.errorHandler (#8777)
* GH-8770: Add `PostgresSubsChannel.errorHandler` Fixes https://github.com/spring-projects/spring-integration/issues/8770 The problem with the `PostgresSubscribableChannel.notifyUpdate()` is that the try-catch block is outside the loop, so the loop will die on an exception, leaving further messages unprocessed. * Add ``PostgresSubscribableChannel.errorHandler` option to be invoked after a `RetryTemplate` and for every failed message. * The `askForMessage()` new logic is to catch an exception on a message and call `errorHandler` returning a `FALLBACK_STUB` to continue an outer loop in the `notifyUpdate()` **Cherry-pick to `6.1.x` & `6.0.x`** * * Rename private `PostgresSubscribableChannel.askForMessage()` method to more specific `pollAndDispatchMessage()`
This commit is contained in:
@@ -31,6 +31,8 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An {@link AbstractSubscribableChannel} for receiving push notifications for
|
||||
@@ -53,6 +55,8 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
|
||||
|
||||
private static final LogAccessor LOGGER = new LogAccessor(PostgresSubscribableChannel.class);
|
||||
|
||||
private static final Optional<?> FALLBACK_STUB = Optional.of(new Object());
|
||||
|
||||
private final JdbcChannelMessageStore jdbcChannelMessageStore;
|
||||
|
||||
private final Object groupId;
|
||||
@@ -65,6 +69,8 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
|
||||
|
||||
private RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(1).build();
|
||||
|
||||
private ErrorHandler errorHandler = ReflectionUtils::rethrowRuntimeException;
|
||||
|
||||
private Executor executor;
|
||||
|
||||
private volatile boolean hasHandlers;
|
||||
@@ -77,6 +83,7 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
|
||||
*/
|
||||
public PostgresSubscribableChannel(JdbcChannelMessageStore jdbcChannelMessageStore,
|
||||
Object groupId, PostgresChannelMessageTableSubscriber messageTableSubscriber) {
|
||||
|
||||
Assert.notNull(jdbcChannelMessageStore, "A jdbcChannelMessageStore must be provided.");
|
||||
Assert.notNull(groupId, "A groupId must be set.");
|
||||
Assert.notNull(messageTableSubscriber, "A messageTableSubscriber must be set.");
|
||||
@@ -117,6 +124,17 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
|
||||
this.retryTemplate = retryTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link ErrorHandler} for messages which cannot be dispatched by this channel.
|
||||
* Used as a recovery callback after {@link RetryTemplate} execution throws an exception.
|
||||
* @param errorHandler the {@link ErrorHandler} to use.
|
||||
* @since 6.0.9
|
||||
*/
|
||||
public void setErrorHandler(ErrorHandler errorHandler) {
|
||||
Assert.notNull(errorHandler, "'errorHandler' must not be null.");
|
||||
this.errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
super.onInit();
|
||||
@@ -160,19 +178,29 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
|
||||
@Override
|
||||
public void notifyUpdate() {
|
||||
this.executor.execute(() -> {
|
||||
try {
|
||||
Optional<Message<?>> dispatchedMessage;
|
||||
do {
|
||||
dispatchedMessage = askForMessage();
|
||||
} while (dispatchedMessage.isPresent());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
LOGGER.error(ex, "Exception during message dispatch");
|
||||
}
|
||||
Optional<?> dispatchedMessage;
|
||||
do {
|
||||
dispatchedMessage = pollAndDispatchMessage();
|
||||
} while (dispatchedMessage.isPresent());
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<Message<?>> askForMessage() {
|
||||
private Optional<?> pollAndDispatchMessage() {
|
||||
try {
|
||||
return doPollAndDispatchMessage();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
try {
|
||||
this.errorHandler.handleError(ex);
|
||||
}
|
||||
catch (Exception ex1) {
|
||||
LOGGER.error(ex, "Exception during message dispatch");
|
||||
}
|
||||
return FALLBACK_STUB;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<?> doPollAndDispatchMessage() {
|
||||
if (this.hasHandlers) {
|
||||
if (this.transactionTemplate != null) {
|
||||
return this.retryTemplate.execute(context ->
|
||||
|
||||
Reference in New Issue
Block a user