GH-3334: Add "embedded reaper" into CorrelationMH (#3342)

* GH-3334: Add "embedded reaper" into CorrelationMH

Fixes https://github.com/spring-projects/spring-integration/issues/3334

* Add `expireTimeout` property into `AbstractCorrelatingMessageHandler`
to call newly introduced `purgeOrphanedGroups()` API for removing old
groups from the store
* Add `expireDuration` to perform `purgeOrphanedGroups()` task periodically

* * Add Java DSL and XML support for `expireTimeout` and `expireDuration` options
* Document the new feature

* * Fix language in docs

Co-authored-by: Gary Russell <grussell@vmware.com>

Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
Artem Bilan
2020-07-20 16:31:24 -04:00
committed by GitHub
parent 6a865b55b9
commit 492f0bfee3
11 changed files with 214 additions and 47 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,14 @@
package org.springframework.integration.aggregator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -31,10 +34,11 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.store.MessageGroup;
@@ -57,7 +61,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
*/
public class AbstractCorrelatingMessageHandlerTests {
@Test // INT-2751
@Test
public void testReaperDoesntReapAProcessingGroup() throws Exception {
final MessageGroupStore groupStore = new SimpleMessageStore();
final CountDownLatch waitForSendLatch = new CountDownLatch(1);
@@ -147,7 +151,7 @@ public class AbstractCorrelatingMessageHandlerTests {
exec.shutdownNow();
}
@Test // INT-2833
@Test
public void testReaperReapsAnEmptyGroup() {
final MessageGroupStore groupStore = new SimpleMessageStore();
AggregatingMessageHandler handler = new AggregatingMessageHandler(group -> group, groupStore);
@@ -176,7 +180,7 @@ public class AbstractCorrelatingMessageHandlerTests {
.isEqualTo(0);
}
@Test // INT-2833
@Test
public void testReaperReapsAnEmptyGroupAfterConfiguredDelay() throws Exception {
final MessageGroupStore groupStore = new SimpleMessageStore();
AggregatingMessageHandler handler = new AggregatingMessageHandler(group -> group, groupStore);
@@ -251,7 +255,7 @@ public class AbstractCorrelatingMessageHandlerTests {
assertThat(payload.size()).isEqualTo(1);
}
@Test /* INT-3216 */
@Test
public void testDontReapIfAlreadyComplete() throws Exception {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
@@ -384,7 +388,7 @@ public class AbstractCorrelatingMessageHandlerTests {
}
@Test
@Ignore("Time sensitive: the empty group might be removed before main thread reaches assertion for size")
@Disabled("Time sensitive: the empty group might be removed before main thread reaches assertion for size")
public void testScheduleRemoveAnEmptyGroupAfterConfiguredDelay() throws Exception {
final MessageGroupStore groupStore = new SimpleMessageStore();
AggregatingMessageHandler handler = new AggregatingMessageHandler(group -> group, groupStore);
@@ -430,7 +434,7 @@ public class AbstractCorrelatingMessageHandlerTests {
}
@Test
@Ignore("Until 5.2 with new 'owner' feature on groups")
@Disabled("Until 5.2 with new 'owner' feature on groups")
public void testDontReapMessageOfOtherHandler() {
MessageGroupStore groupStore = new SimpleMessageStore();
@@ -484,4 +488,45 @@ public class AbstractCorrelatingMessageHandlerTests {
assertThat(receive.getHeaders().containsKey(IntegrationMessageHeaderAccessor.SEQUENCE_DETAILS)).isTrue();
}
@Test
public void testPurgeOrphanedGroupsOnStartup() throws InterruptedException {
MessageGroupStore groupStore = new SimpleMessageStore();
AggregatingMessageHandler handler = new AggregatingMessageHandler(group -> group, groupStore);
handler.setReleaseStrategy(group -> false);
QueueChannel discardChannel = new QueueChannel();
handler.setDiscardChannel(discardChannel);
handler.setExpireTimeout(1);
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.handleMessageInternal(MessageBuilder.withPayload("test").setCorrelationId("test").build());
Thread.sleep(100);
handler.start();
Message<?> receive = discardChannel.receive(10000);
assertThat(receive).isNotNull();
assertThat(groupStore.getMessageGroupCount()).isEqualTo(0);
}
@Test
public void testPurgeOrphanedGroupsScheduled() {
MessageGroupStore groupStore = spy(new SimpleMessageStore());
AggregatingMessageHandler handler = new AggregatingMessageHandler(group -> group, groupStore);
handler.setReleaseStrategy(group -> false);
QueueChannel discardChannel = new QueueChannel();
handler.setDiscardChannel(discardChannel);
handler.setExpireTimeout(100);
handler.setExpireDuration(Duration.ofMillis(10));
handler.setBeanFactory(mock(BeanFactory.class));
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.afterPropertiesSet();
handler.setTaskScheduler(taskScheduler);
handler.afterPropertiesSet();
handler.handleMessageInternal(MessageBuilder.withPayload("test").setCorrelationId("test").build());
handler.start();
Message<?> receive = discardChannel.receive(10000);
assertThat(receive).isNotNull();
assertThat(groupStore.getMessageGroupCount()).isEqualTo(0);
verify(groupStore, atLeast(2)).expireMessageGroups(100);
taskScheduler.destroy();
}
}

View File

@@ -56,7 +56,9 @@
scheduler="scheduler"
message-store="store"
pop-sequence="false"
order="5">
order="5"
expire-duration="10000"
expire-timeout="250">
<expire-transactional/>
</aggregator>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,13 @@ package org.springframework.integration.config;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanCreationException;
@@ -54,7 +54,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* @author Marius Bogoevici
@@ -65,7 +65,7 @@ import org.springframework.test.context.junit4.SpringRunner;
* @author Gunnar Hillert
* @author Gary Russell
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
public class AggregatorParserTests {
@Autowired
@@ -194,6 +194,9 @@ public class AggregatorParserTests {
assertThat(TestUtils.getPropertyValue(consumer, "order")).isEqualTo(5);
assertThat(TestUtils.getPropertyValue(consumer, "forceReleaseAdviceChain")).isNotNull();
assertThat(TestUtils.getPropertyValue(consumer, "popSequence", Boolean.class)).isFalse();
assertThat(TestUtils.getPropertyValue(consumer, "expireTimeout")).isEqualTo(250L);
assertThat(TestUtils.getPropertyValue(consumer, "expireDuration", Duration.class))
.isEqualTo(Duration.ofSeconds(10));
}
@Test