Fix PostgresSubscribableChannel race condition

The `PostgresSubscribableChannel` uses a task executor
for dispatching messages.
Even if we stop `PostgresChannelMessageTableSubscriber` and
unsubscribe from the channel, the task might be ongoing.

* Use explicit `ThreadPoolTaskExecutor` in the test
to shout it down and wait for tasks to be completed before
verifying DB status
* Optimize `PostgresSubscribableChannel` to mark TX for rollback
when we got a message from DB, but no handlers subscribed

# Conflicts:
#	spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/channel/PostgresChannelMessageTableSubscriberTests.java
This commit is contained in:
Artem Bilan
2023-10-17 13:25:05 -04:00
parent 1aac947aad
commit 012a03ff77
2 changed files with 32 additions and 8 deletions

View File

@@ -168,7 +168,16 @@ public class PostgresSubscribableChannel extends AbstractSubscribableChannel
if (this.hasHandlers) {
if (this.transactionTemplate != null) {
return this.retryTemplate.execute(context ->
this.transactionTemplate.execute(status -> pollMessage().map(this::dispatch)));
this.transactionTemplate.execute(status ->
pollMessage()
.filter(message -> {
if (!this.hasHandlers) {
status.setRollbackOnly();
return false;
}
return true;
})
.map(this::dispatch)));
}
else {
return pollMessage()

View File

@@ -48,6 +48,7 @@ import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.PlatformTransactionManager;
@@ -109,6 +110,8 @@ public class PostgresChannelMessageTableSubscriberTests implements PostgresConta
private PostgresSubscribableChannel postgresSubscribableChannel;
private ThreadPoolTaskExecutor taskExecutor;
private String groupId;
@BeforeEach
@@ -121,15 +124,26 @@ public class PostgresChannelMessageTableSubscriberTests implements PostgresConta
POSTGRES_CONTAINER.getPassword())
.unwrap(PgConnection.class));
this.taskExecutor = new ThreadPoolTaskExecutor();
this.taskExecutor.setCorePoolSize(10);
this.taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
this.taskExecutor.setAwaitTerminationSeconds(10);
this.taskExecutor.afterPropertiesSet();
this.groupId = testInfo.getDisplayName();
this.postgresSubscribableChannel =
new PostgresSubscribableChannel(messageStore, groupId, postgresChannelMessageTableSubscriber);
this.postgresSubscribableChannel.setBeanName("testPostgresChannel");
this.postgresSubscribableChannel.setDispatcherExecutor(this.taskExecutor);
this.postgresSubscribableChannel.afterPropertiesSet();
}
@AfterEach
void tearDown() {
this.postgresChannelMessageTableSubscriber.stop();
this.taskExecutor.shutdown();
}
@@ -172,13 +186,13 @@ public class PostgresChannelMessageTableSubscriberTests implements PostgresConta
postgresChannelMessageTableSubscriber.start();
MessageHandler messageHandler =
message -> {
try {
throw new RuntimeException("An error has occurred");
}
finally {
latch.countDown();
}
};
try {
throw new RuntimeException("An error has occurred");
}
finally {
latch.countDown();
}
};
postgresSubscribableChannel.subscribe(messageHandler);
messageStore.addMessageToGroup(groupId, new GenericMessage<>("1"));
@@ -189,6 +203,7 @@ public class PostgresChannelMessageTableSubscriberTests implements PostgresConta
// Stop subscriber to unlock records from TX for the next verification
postgresChannelMessageTableSubscriber.stop();
postgresSubscribableChannel.unsubscribe(messageHandler);
this.taskExecutor.shutdown();
assertThat(messageStore.messageGroupSize(groupId)).isEqualTo(2);
assertThat(messageStore.pollMessageFromGroup(groupId).getPayload()).isEqualTo("1");