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:
Artem Bilan
2023-10-25 13:26:24 -04:00
committed by GitHub
parent 4b27a3c521
commit 769367deea
2 changed files with 69 additions and 12 deletions

View File

@@ -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 ->