INT-4452: Expire immediately when group timeout<0

JIRA: https://jira.spring.io/browse/INT-4452

This is pretty typical in practice to get a `groupTimeout` to be
evaluated to a negative value: some business decisions, sensitive
calculations and so on.

* Treat any non-positive `groupTimeout` as a signal to expire group
immediately without scheduling.
Only `null` is considered as a signal do nothing for the current message
* Polishing for some tests for better performance - saves some execution
time
This commit is contained in:
Artem Bilan
2018-06-22 17:12:50 -04:00
committed by Gary Russell
parent ee501c801f
commit 13510df575
15 changed files with 161 additions and 121 deletions

View File

@@ -100,7 +100,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
private final Map<UUID, ScheduledFuture<?>> expireGroupScheduledFutures = new ConcurrentHashMap<>();
private final Set<Object> groupIds = ConcurrentHashMap.newKeySet();
private final Set<Object> groupIds = ConcurrentHashMap.newKeySet();
private MessageGroupProcessor outputProcessor;
@@ -144,15 +144,23 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store,
CorrelationStrategy correlationStrategy, ReleaseStrategy releaseStrategy) {
Assert.notNull(processor, "'processor' must not be null");
Assert.notNull(store, "'store' must not be null");
setMessageStore(store);
this.outputProcessor = processor;
this.correlationStrategy = (correlationStrategy == null
? new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID)
: correlationStrategy);
this.releaseStrategy = releaseStrategy == null ? new SimpleSequenceSizeReleaseStrategy() : releaseStrategy;
this.correlationStrategy =
correlationStrategy == null
? new HeaderAttributeCorrelationStrategy(IntegrationMessageHeaderAccessor.CORRELATION_ID)
: correlationStrategy;
this.releaseStrategy =
releaseStrategy == null
? new SimpleSequenceSizeReleaseStrategy()
: releaseStrategy;
this.releaseStrategySet = releaseStrategy != null;
this.sequenceAware = this.releaseStrategy instanceof SequenceSizeReleaseStrategy;
}
@@ -195,7 +203,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
}
public void setForceReleaseAdviceChain(List<Advice> forceReleaseAdviceChain) {
Assert.notNull(forceReleaseAdviceChain, "forceReleaseAdviceChain must not be null");
Assert.notNull(forceReleaseAdviceChain, "'forceReleaseAdviceChain' must not be null");
this.forceReleaseAdviceChain = forceReleaseAdviceChain;
}
@@ -242,7 +250,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
}
if (this.releasePartialSequences) {
Assert.isInstanceOf(SequenceSizeReleaseStrategy.class, this.releaseStrategy,
Assert.isInstanceOf(SequenceSizeReleaseStrategy.class, this.releaseStrategy, () ->
"Release strategy of type [" + this.releaseStrategy.getClass().getSimpleName() +
"] cannot release partial sequences. Use a SequenceSizeReleaseStrategy instead.");
((SequenceSizeReleaseStrategy) this.releaseStrategy)
@@ -519,7 +527,7 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
* When 'groupTimeout' is evaluated to 'null' we do nothing.
* The 'MessageGroupStoreReaper' can be used to 'forceComplete' message groups.
*/
if (groupTimeout != null && groupTimeout >= 0) {
if (groupTimeout != null) {
if (groupTimeout > 0) {
final Object groupId = messageGroup.getGroupId();
final long timestamp = messageGroup.getTimestamp();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -47,6 +47,7 @@ import org.springframework.messaging.MessageHeaders;
* @author Mark Fisher
* @author Marius Bogoevici
* @author Iwein Fuld
* @author Artem Bilan
*/
public class ConcurrentAggregatorTests {
@@ -90,8 +91,7 @@ public class ConcurrentAggregatorTests {
@Test
@Ignore
// dropped backwards compatibility for duplicate ID's
public void testCompleteGroupWithinTimeoutWithSameId()
throws InterruptedException {
public void testCompleteGroupWithinTimeoutWithSameId() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel,
"ID#1");
@@ -111,8 +111,8 @@ public class ConcurrentAggregatorTests {
}
@Test
public void testShouldNotSendPartialResultOnTimeoutByDefault()
throws InterruptedException {
public void testShouldNotSendPartialResultOnTimeoutByDefault() throws InterruptedException {
QueueChannel discardChannel = new QueueChannel();
this.aggregator.setDiscardChannel(discardChannel);
QueueChannel replyChannel = new QueueChannel();
@@ -126,17 +126,16 @@ public class ConcurrentAggregatorTests {
assertEquals("Task should have completed within timeout", 0, latch
.getCount());
Message<?> reply = replyChannel.receive(1000);
Message<?> reply = replyChannel.receive(10);
assertNull("No message should have been sent normally", reply);
this.store.expireMessageGroups(-10000);
Message<?> discardedMessage = discardChannel.receive(1000);
Message<?> discardedMessage = discardChannel.receive(10000);
assertNotNull("A message should have been discarded", discardedMessage);
assertEquals(message, discardedMessage);
}
@Test
public void testShouldSendPartialResultOnTimeoutTrue()
throws InterruptedException {
public void testShouldSendPartialResultOnTimeoutTrue() throws InterruptedException {
this.aggregator.setSendPartialResultOnExpiry(true);
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
@@ -252,16 +251,14 @@ public class ConcurrentAggregatorTests {
}
@Test(expected = MessageHandlingException.class)
public void testExceptionThrownIfNoCorrelationId()
throws InterruptedException {
public void testExceptionThrownIfNoCorrelationId() {
Message<?> message = createMessage(3, null, 2, 1, new QueueChannel(),
null);
this.aggregator.handleMessage(message);
}
@Test
public void testAdditionalMessageAfterCompletion()
throws InterruptedException {
public void testAdditionalMessageAfterCompletion() throws InterruptedException {
QueueChannel replyChannel = new QueueChannel();
Message<?> message1 = createMessage(3, "ABC", 3, 1, replyChannel, null);
Message<?> message2 = createMessage(5, "ABC", 3, 2, replyChannel, null);
@@ -291,6 +288,7 @@ public class ConcurrentAggregatorTests {
private static Message<?> createMessage(Object payload,
Object correlationId, int sequenceSize, int sequenceNumber,
MessageChannel replyChannel, String predefinedId) {
MessageBuilder<Object> builder = MessageBuilder.withPayload(payload)
.setCorrelationId(correlationId).setSequenceSize(sequenceSize)
.setSequenceNumber(sequenceNumber)

View File

@@ -42,7 +42,7 @@
<aggregator input-channel="groupTimeoutExpressionAggregatorInput" output-channel="output" discard-channel="discard"
send-partial-result-on-expiry="true"
group-timeout-expression="size() ge 2 ? 100 : -1"
group-timeout-expression="size() ge 2 ? 100 : null"
release-strategy-expression="messages[0].headers.sequenceNumber == messages[0].headers.sequenceSize"/>
<aggregator input-channel="zeroGroupTimeoutExpressionAggregatorInput" output-channel="output" discard-channel="discard"

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -45,8 +45,7 @@ import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Iwein Fuld
@@ -55,8 +54,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Artem Bilan
* @author Gary Russell
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@RunWith(SpringRunner.class)
@DirtiesContext
public class AggregatorIntegrationTests {
@@ -91,10 +89,10 @@ public class AggregatorIntegrationTests {
private QueueChannel errors;
@Test
public void testVanillaAggregation() throws Exception {
public void testVanillaAggregation() {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
input.send(new GenericMessage<Integer>(i, headers));
input.send(new GenericMessage<>(i, headers));
}
Message<?> receive = output.receive(10000);
assertNotNull(receive);
@@ -102,10 +100,10 @@ public class AggregatorIntegrationTests {
}
@Test
public void testNonExpiringAggregator() throws Exception {
public void testNonExpiringAggregator() {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
nonExpiringAggregatorInput.send(new GenericMessage<>(i, headers));
}
assertNotNull(output.receive(0));
@@ -113,7 +111,7 @@ public class AggregatorIntegrationTests {
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
nonExpiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
nonExpiringAggregatorInput.send(new GenericMessage<>(i, headers));
}
assertNull(output.receive(0));
@@ -125,10 +123,10 @@ public class AggregatorIntegrationTests {
}
@Test
public void testExpiringAggregator() throws Exception {
public void testExpiringAggregator() {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
expiringAggregatorInput.send(new GenericMessage<>(i, headers));
}
assertNotNull(output.receive(0));
@@ -136,7 +134,7 @@ public class AggregatorIntegrationTests {
for (int i = 5; i < 10; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
expiringAggregatorInput.send(new GenericMessage<Integer>(i, headers));
expiringAggregatorInput.send(new GenericMessage<>(i, headers));
}
assertNotNull(output.receive(0));
@@ -148,7 +146,7 @@ public class AggregatorIntegrationTests {
public void testGroupTimeoutScheduling() throws Exception {
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = stubHeaders(i, 5, 1);
this.groupTimeoutAggregatorInput.send(new GenericMessage<Integer>(i, headers));
this.groupTimeoutAggregatorInput.send(new GenericMessage<>(i, headers));
//Wait until 'group-timeout' does its stuff.
MessageGroupStore mgs = TestUtils.getPropertyValue(this.context.getBean("gta.handler"), "messageStore",
@@ -166,11 +164,11 @@ public class AggregatorIntegrationTests {
@Test
public void testGroupTimeoutReschedulingOnMessageDeliveryException() throws Exception {
for (int i = 0; i < 5; i++) {
this.output.send(new GenericMessage<String>("fake message"));
this.output.send(new GenericMessage<>("fake message"));
}
Map<String, Object> headers = stubHeaders(1, 2, 1);
this.groupTimeoutAggregatorInput.send(new GenericMessage<Integer>(1, headers));
this.groupTimeoutAggregatorInput.send(new GenericMessage<>(1, headers));
//Wait until 'group-timeout' does its stuff.
MessageGroupStore mgs = TestUtils.getPropertyValue(this.context.getBean("gta.handler"), "messageStore",
@@ -190,14 +188,14 @@ public class AggregatorIntegrationTests {
}
@Test
public void testGroupTimeoutExpressionScheduling() throws Exception {
// Since group-timeout-expression="size() >= 2 ? 100 : -1". The first message won't be scheduled to 'forceComplete'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(1, stubHeaders(1, 6, 1)));
public void testGroupTimeoutExpressionScheduling() {
// Since group-timeout-expression="size() >= 2 ? 100 : null". The first message won't be scheduled to 'forceComplete'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(1, stubHeaders(1, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
// As far as 'group.size() >= 2' it will be scheduled to 'forceComplete'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(2, stubHeaders(2, 6, 1)));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(2, stubHeaders(2, 6, 1)));
assertNull(this.output.receive(0));
Message<?> receive = this.output.receive(10000);
assertNotNull(receive);
@@ -205,15 +203,15 @@ public class AggregatorIntegrationTests {
assertNull(this.discard.receive(0));
// The same with these three messages
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(3, stubHeaders(3, 6, 1)));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(3, stubHeaders(3, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(4, stubHeaders(4, 6, 1)));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(4, stubHeaders(4, 6, 1)));
assertNull(this.output.receive(0));
assertNull(this.discard.receive(0));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(5, stubHeaders(5, 6, 1)));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(5, stubHeaders(5, 6, 1)));
assertNull(this.output.receive(0));
receive = this.output.receive(10000);
assertNotNull(receive);
@@ -221,7 +219,7 @@ public class AggregatorIntegrationTests {
assertNull(this.discard.receive(0));
// The last message in the sequence - normal release by provided 'ReleaseStrategy'
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(6, stubHeaders(6, 6, 1)));
this.groupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(6, stubHeaders(6, 6, 1)));
receive = this.output.receive(10000);
assertNotNull(receive);
assertEquals(1, ((Collection<?>) receive.getPayload()).size());
@@ -229,17 +227,17 @@ public class AggregatorIntegrationTests {
}
@Test
public void testZeroGroupTimeoutExpressionScheduling() throws Exception {
public void testZeroGroupTimeoutExpressionScheduling() {
try {
this.output.purge(null);
this.errors.purge(null);
GenericMessage<String> message = new GenericMessage<String>("foo");
GenericMessage<String> message = new GenericMessage<>("foo");
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.output.send(message);
this.zeroGroupTimeoutExpressionAggregatorInput.send(new GenericMessage<Integer>(1, stubHeaders(1, 2, 1)));
this.zeroGroupTimeoutExpressionAggregatorInput.send(new GenericMessage<>(1, stubHeaders(1, 2, 1)));
ErrorMessage em = (ErrorMessage) this.errors.receive(10000);
assertNotNull(em);
assertThat(em.getPayload().getMessage().toLowerCase(),
@@ -263,7 +261,7 @@ public class AggregatorIntegrationTests {
}
private Map<String, Object> stubHeaders(int sequenceNumber, int sequenceSize, int correlationId) {
Map<String, Object> headers = new HashMap<String, Object>();
Map<String, Object> headers = new HashMap<>();
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, sequenceNumber);
headers.put(IntegrationMessageHeaderAccessor.SEQUENCE_SIZE, sequenceSize);
headers.put(IntegrationMessageHeaderAccessor.CORRELATION_ID, correlationId);

View File

@@ -38,25 +38,26 @@ import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
*/
public class PriorityChannelTests {
@Test
public void testCapacityEnforced() {
PriorityChannel channel = new PriorityChannel(3);
assertTrue(channel.send(new GenericMessage<String>("test1"), 0));
assertTrue(channel.send(new GenericMessage<String>("test2"), 0));
assertTrue(channel.send(new GenericMessage<String>("test3"), 0));
assertFalse(channel.send(new GenericMessage<String>("test4"), 0));
assertTrue(channel.send(new GenericMessage<>("test1"), 0));
assertTrue(channel.send(new GenericMessage<>("test2"), 0));
assertTrue(channel.send(new GenericMessage<>("test3"), 0));
assertFalse(channel.send(new GenericMessage<>("test4"), 0));
channel.receive(0);
assertTrue(channel.send(new GenericMessage<String>("test5")));
assertTrue(channel.send(new GenericMessage<>("test5")));
}
@Test
public void testDefaultComparatorWithTimestampFallback() throws Exception {
public void testDefaultComparatorWithTimestampFallback() {
PriorityChannel channel = new PriorityChannel();
for (int i = 0; i < 1000; i++) {
channel.send(new GenericMessage<Integer>(i));
channel.send(new GenericMessage<>(i));
}
for (int i = 0; i < 1000; i++) {
assertEquals(i, channel.receive().getPayload());
@@ -86,9 +87,9 @@ public class PriorityChannelTests {
// although this test has no assertions it results in ConcurrentModificationException
// if executed before changes for INT-2508
@Test
public void testPriorityChannelWithConcurrentModification() throws Exception {
public void testPriorityChannelWithConcurrentModification() {
final PriorityChannel channel = new PriorityChannel();
final Message<String> message = new GenericMessage<String>("hello");
final Message<String> message = new GenericMessage<>("hello");
for (int i = 0; i < 1000; i++) {
channel.send(message);
new Thread(() -> channel.receive()).start();
@@ -99,11 +100,11 @@ public class PriorityChannelTests {
@Test
public void testCustomComparator() {
PriorityChannel channel = new PriorityChannel(5, new StringPayloadComparator());
Message<?> messageA = new GenericMessage<String>("A");
Message<?> messageB = new GenericMessage<String>("B");
Message<?> messageC = new GenericMessage<String>("C");
Message<?> messageD = new GenericMessage<String>("D");
Message<?> messageE = new GenericMessage<String>("E");
Message<?> messageA = new GenericMessage<>("A");
Message<?> messageB = new GenericMessage<>("B");
Message<?> messageC = new GenericMessage<>("C");
Message<?> messageD = new GenericMessage<>("D");
Message<?> messageE = new GenericMessage<>("E");
channel.send(messageC);
channel.send(messageA);
channel.send(messageE);
@@ -200,7 +201,7 @@ public class PriorityChannelTests {
PriorityChannel channel = new PriorityChannel(5);
Message<?> highPriority = createPriorityMessage(5);
Message<?> lowPriority = createPriorityMessage(-5);
Message<?> nullPriority = new GenericMessage<String>("test:NULL");
Message<?> nullPriority = new GenericMessage<>("test:NULL");
channel.send(lowPriority);
channel.send(highPriority);
channel.send(nullPriority);
@@ -214,7 +215,7 @@ public class PriorityChannelTests {
PriorityChannel channel = new PriorityChannel();
Message<?> highPriority = createPriorityMessage(5);
Message<?> lowPriority = createPriorityMessage(-5);
Message<?> nullPriority = new GenericMessage<String>("test:NULL");
Message<?> nullPriority = new GenericMessage<>("test:NULL");
channel.send(lowPriority);
channel.send(highPriority);
channel.send(nullPriority);
@@ -228,8 +229,8 @@ public class PriorityChannelTests {
final PriorityChannel channel = new PriorityChannel(1);
final AtomicBoolean sentSecondMessage = new AtomicBoolean(false);
ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
channel.send(new GenericMessage<String>("test-1"));
executor.execute(() -> sentSecondMessage.set(channel.send(new GenericMessage<String>("test-2"), 10)));
channel.send(new GenericMessage<>("test-1"));
executor.execute(() -> sentSecondMessage.set(channel.send(new GenericMessage<>("test-2"), 10)));
assertFalse(sentSecondMessage.get());
executor.shutdown();
@@ -249,15 +250,15 @@ public class PriorityChannelTests {
ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
channel.send(new GenericMessage<String>("test-1"));
executor.execute(() -> {
sentSecondMessage.set(channel.send(new GenericMessage<String>("test-2"), 3000));
sentSecondMessage.set(channel.send(new GenericMessage<>("test-2"), 3000));
latch.countDown();
});
assertFalse(sentSecondMessage.get());
Thread.sleep(500);
Thread.sleep(10);
Message<?> message1 = channel.receive();
assertNotNull(message1);
assertEquals("test-1", message1.getPayload());
latch.await(1000, TimeUnit.MILLISECONDS);
assertTrue(latch.await(10000, TimeUnit.MILLISECONDS));
assertTrue(sentSecondMessage.get());
Message<?> message2 = channel.receive();
assertNotNull(message2);
@@ -271,10 +272,10 @@ public class PriorityChannelTests {
final AtomicBoolean sentSecondMessage = new AtomicBoolean(false);
ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
channel.send(new GenericMessage<String>("test-1"));
executor.execute(() -> sentSecondMessage.set(channel.send(new GenericMessage<String>("test-2"), -1)));
executor.execute(() -> sentSecondMessage.set(channel.send(new GenericMessage<>("test-2"), -1)));
assertFalse(sentSecondMessage.get());
Thread.sleep(500);
Message<?> message1 = channel.receive(1000);
Thread.sleep(10);
Message<?> message1 = channel.receive(10000);
assertNotNull(message1);
assertEquals("test-1", message1.getPayload());
executor.shutdown();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2018 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.
@@ -52,6 +52,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
*
* @since 2.0
*/
@ContextConfiguration
@@ -92,7 +93,7 @@ public class ControlBusTests {
"ControlBusLifecycleTests-context.xml", this.getClass());
MessageChannel inputChannel = context.getBean("inputChannel", MessageChannel.class);
PollableChannel outputChannel = context.getBean("outputChannel", PollableChannel.class);
assertNull(outputChannel.receive(1000));
assertNull(outputChannel.receive(10));
Message<?> message = MessageBuilder.withPayload("@adapter.start()").build();
inputChannel.send(message);
assertNotNull(outputChannel.receive(1000));
@@ -109,7 +110,7 @@ public class ControlBusTests {
assertEquals(0, result.getPayload());
this.registry.channelToChannelName(new DirectChannel());
// Sleep a bit to be sure that we aren't reaped by registry TTL as 60000
Thread.sleep(100);
Thread.sleep(10);
messagingTemplate.convertAndSend(input, "@integrationHeaderChannelRegistry.size()");
result = this.output.receive(0);
assertNotNull(result);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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,12 +17,15 @@
package org.springframework.integration.config.xml;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.channel.QueueChannel;
@@ -37,6 +40,7 @@ import org.springframework.messaging.support.GenericMessage;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Artem Bilan
*
*/
public class PollerWithErrorChannelTests {
@@ -54,10 +58,20 @@ public class PollerWithErrorChannelTests {
SubscribableChannel errorChannel = ac.getBean("errorChannel", SubscribableChannel.class);
MessageHandler handler = mock(MessageHandler.class);
CountDownLatch handleLatch = new CountDownLatch(1);
willAnswer(invocation -> {
handleLatch.countDown();
return null;
})
.given(handler)
.handleMessage(any(Message.class));
errorChannel.subscribe(handler);
adapter.start();
Thread.sleep(1000);
verify(handler, atLeastOnce()).handleMessage(Mockito.any(Message.class));
assertTrue(handleLatch.await(10, TimeUnit.SECONDS));
adapter.stop();
ac.close();
}
@@ -75,7 +89,7 @@ public class PollerWithErrorChannelTests {
}
@Test
public void testWithErrorChannelAndHeader() throws Exception {
public void testWithErrorChannelAndHeader() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml",
this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeader",
@@ -89,7 +103,7 @@ public class PollerWithErrorChannelTests {
@Test
// config the same as above but the error wil come from the send
public void testWithErrorChannelAndHeaderWithSendFailure() throws Exception {
public void testWithErrorChannelAndHeaderWithSendFailure() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml",
this.getClass());
SourcePollingChannelAdapter adapter = ac.getBean("withErrorChannelAndHeaderErrorOnSend",
@@ -103,12 +117,12 @@ public class PollerWithErrorChannelTests {
@Test
// INT-1952
public void testWithErrorChannelAndPollingConsumer() throws Exception {
public void testWithErrorChannelAndPollingConsumer() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("PollerWithErrorChannel-context.xml",
this.getClass());
MessageChannel serviceWithPollerChannel = ac.getBean("serviceWithPollerChannel", MessageChannel.class);
QueueChannel errorChannel = ac.getBean("serviceErrorChannel", QueueChannel.class);
serviceWithPollerChannel.send(new GenericMessage<String>(""));
serviceWithPollerChannel.send(new GenericMessage<>(""));
assertNotNull(errorChannel.receive(10000));
ac.close();
}

View File

@@ -152,7 +152,7 @@ public class PollerAdviceTests {
adapter.setAdviceChain(adviceChain);
adapter.afterPropertiesSet();
adapter.start();
assertFalse(latch.await(1, TimeUnit.SECONDS));
assertFalse(latch.await(10, TimeUnit.MILLISECONDS));
adapter.stop();
skipper.reset();
latch = new CountDownLatch(1);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2018 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.
@@ -60,7 +60,7 @@ public class PollingLifecycleTests {
private final TestErrorHandler errorHandler = new TestErrorHandler();
@Before
public void init() throws Exception {
public void init() {
taskScheduler.afterPropertiesSet();
}
@@ -68,13 +68,16 @@ public class PollingLifecycleTests {
public void ensurePollerTaskStops() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
QueueChannel channel = new QueueChannel();
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<>("foo"));
//Has to be an explicit implementation - Mockito cannot mock/spy lambdas
MessageHandler handler = Mockito.spy(new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
latch.countDown();
}
});
PollingConsumer consumer = new PollingConsumer(channel, handler);
consumer.setTrigger(new PeriodicTrigger(0));
@@ -87,7 +90,7 @@ public class PollingLifecycleTests {
Mockito.verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
consumer.stop();
for (int i = 0; i < 10; i++) {
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<>("foo"));
}
Thread.sleep(2000); // give enough time for poller to kick in if it didn't stop properly
// we'll still have a natural race condition between call to stop() and poller polling
@@ -105,12 +108,16 @@ public class PollingLifecycleTests {
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(2000));
adapterFactory.setPollerMetadata(pollerMetadata);
//Has to be an explicit implementation - Mockito cannot mock/spy lambdas
MessageSource<String> source = spy(new MessageSource<String>() {
@Override
public Message<String> receive() {
latch.countDown();
return new GenericMessage<String>("hello");
return new GenericMessage<>("hello");
}
});
adapterFactory.setSource(source);
adapterFactory.setOutputChannel(channel);
@@ -122,7 +129,7 @@ public class PollingLifecycleTests {
assertTrue(latch.await(20, TimeUnit.SECONDS));
assertNotNull(channel.receive(100));
adapter.stop();
assertNull(channel.receive(1000));
assertNull(channel.receive(10));
Mockito.verify(source, times(1)).receive();
}
@@ -136,20 +143,20 @@ public class PollingLifecycleTests {
pollerMetadata.setMaxMessagesPerPoll(-1);
pollerMetadata.setTrigger(new PeriodicTrigger(2000));
adapterFactory.setPollerMetadata(pollerMetadata);
final Runnable coughtInterrupted = mock(Runnable.class);
final Runnable caughtInterrupted = mock(Runnable.class);
MessageSource<String> source = () -> {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
Thread.sleep(10);
latch.countDown();
}
}
catch (InterruptedException e) {
coughtInterrupted.run();
caughtInterrupted.run();
}
return new GenericMessage<String>("hello");
return new GenericMessage<>("hello");
};
adapterFactory.setSource(source);
adapterFactory.setOutputChannel(channel);
@@ -161,8 +168,8 @@ public class PollingLifecycleTests {
assertTrue(latch.await(3000, TimeUnit.SECONDS));
//
adapter.stop();
Thread.sleep(1000);
Mockito.verify(coughtInterrupted, times(1)).run();
Thread.sleep(10);
Mockito.verify(caughtInterrupted, times(1)).run();
}
@Test

View File

@@ -13,7 +13,7 @@
default-request-channel="requestChannel"
default-reply-timeout="3000"
service-interface="org.springframework.integration.gateway.GatewayRequiresReplyTests$TestService" />
<service-activator input-channel="requestChannel"
expression="payload == 'foo' ? 'bar' : null"
@@ -21,16 +21,16 @@
<gateway id="timeoutGateway"
default-request-channel="timeoutChannel"
default-reply-timeout="1000"
default-reply-timeout="10"
service-interface="org.springframework.integration.gateway.GatewayRequiresReplyTests$TestService" />
<channel id="timeoutChannel">
<dispatcher task-executor="executor"/>
</channel>
<service-activator input-channel="timeoutChannel">
<beans:bean class="org.springframework.integration.gateway.GatewayRequiresReplyTests.LongRunningService"/>
</service-activator>
<task:executor id="executor" pool-size="5"/>
</beans:beans>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@@ -16,8 +16,8 @@
package org.springframework.integration.gateway;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +31,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Mark Fisher
* @author Gunnar Hillert
* @author Artem Bilan
*
* @since 2.0
*/
@ContextConfiguration
@@ -43,34 +45,38 @@ public class GatewayRequiresReplyTests {
@Test
public void replyReceived() {
TestService gateway = (TestService) applicationContext.getBean("gateway");
TestService gateway = this.applicationContext.getBean("gateway", TestService.class);
String result = gateway.test("foo");
assertEquals("bar", result);
}
@Test(expected = ReplyRequiredException.class)
public void noReplyReceived() {
TestService gateway = (TestService) applicationContext.getBean("gateway");
TestService gateway = this.applicationContext.getBean("gateway", TestService.class);
gateway.test("bad");
}
@Test
public void timedOutGateway() {
TestService gateway = (TestService) applicationContext.getBean("timeoutGateway");
TestService gateway = this.applicationContext.getBean("timeoutGateway", TestService.class);
String result = gateway.test("hello");
assertNull(result);
}
public interface TestService {
String test(String s);
}
public static class LongRunningService {
public String echo(String value) throws Exception {
Thread.sleep(5000);
return value;
}
}
}

View File

@@ -425,17 +425,17 @@ public class DelayHandlerTests {
It's difficult to test it from real ctx, because any async process from 'inbound-channel-adapter'
can't achieve the DelayHandler before the main thread emits 'ContextRefreshedEvent'.
*/
public void testRescheduleAndHandleAtTheSameTime() throws Exception {
public void testRescheduleAndHandleAtTheSameTime() {
QueueChannel results = new QueueChannel();
delayHandler.setOutputChannel(results);
this.delayHandler.setDefaultDelay(100);
this.delayHandler.setDefaultDelay(10);
startDelayerHandler();
this.input.send(new GenericMessage<>("foo"));
this.delayHandler.reschedulePersistedMessages();
Message<?> message = results.receive(10000);
assertNotNull(message);
message = results.receive(500);
message = results.receive(50);
assertNull(message);
}

View File

@@ -42,6 +42,7 @@ import org.springframework.messaging.MessageChannel;
/**
* @author Gary Russell
*
* @since 3.0
*
*/
@@ -61,11 +62,11 @@ public class RouterConcurrencyTest {
protected void setConversionService(ConversionService conversionService) {
try {
if (count.incrementAndGet() > 1) {
Thread.sleep(2000);
Thread.sleep(20);
}
super.setConversionService(conversionService);
semaphore.release();
Thread.sleep(1000);
Thread.sleep(10);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
@@ -85,8 +86,7 @@ public class RouterConcurrencyTest {
router.setBeanFactory(beanFactory);
ExecutorService exec = Executors.newFixedThreadPool(2);
final List<ConversionService> returns = Collections.synchronizedList(
new ArrayList<ConversionService>());
final List<ConversionService> returns = Collections.synchronizedList(new ArrayList<>());
Runnable runnable = () -> {
ConversionService requiredConversionService = router.getRequiredConversionService();
returns.add(requiredConversionService);

View File

@@ -477,8 +477,8 @@ See <<redis-lock-registry>>, <<gemfire-lock-registry>>, <<zk-lock-registry>> for
This attribute provides a built-in _Time-base Release Strategy_ for the aggregator, when there is a need to emit a partial result (or discard the group), if a new Message does not arrive for the `MessageGroup` within the timeout.
When a new Message arrives at the aggregator, any existing `ScheduledFuture<?>` for its `MessageGroup` is canceled.
If the `ReleaseStrategy` returns `false` (don't release) and the `groupTimeout > 0` a new task will be scheduled to expire the group.
Setting this attribute to zero is not advised because it will effectively disable the aggregator because every message group will be immediately completed.
It is possible, however to conditionally set it to zero using an expression; see `group-timeout-expression` for information.
Setting this attribute to zero (or negative value) is not advised because it will effectively disable the aggregator because every message group will be immediately completed.
It is possible, however to conditionally set it to zero (or negative value) using an expression; see `group-timeout-expression` for information.
The action taken during the completion depends on the `ReleaseStrategy` and the `send-partial-group-on-expiry` attribute.
See <<agg-and-group-to>> for more information.
Mutually exclusive with 'group-timeout-expression' attribute.
@@ -486,7 +486,7 @@ Mutually exclusive with 'group-timeout-expression' attribute.
<22> The SpEL expression that evaluates to a `groupTimeout` with the `MessageGroup` as the `#root` evaluation context object.
Used for scheduling the `MessageGroup` to be forced complete.
If the expression evaluates to null or `< 0`, the completion is not scheduled.
If the expression evaluates to `null`, the completion is not scheduled.
If it evaluates to zero, the group is completed immediately on the current thread.
In effect, this provides a dynamic `group-timeout` property.
See `group-timeout` for more information.

View File

@@ -63,4 +63,11 @@ See <<amqp-content-type>> for more information.
A confusing `max-rows-per-poll` property on the JDBC Inbound Channel Adapter and JDBC Outbound Gateway has been deprecated in favor newly introduced `max-rows` property.
See <<jdbc>> for more information.
See <<jdbc>> for more information.
==== Aggregator Changes
An aggregator now expires the group immediately, if the `groupTimeout` is evaluated to a negative value.
Only `null` is considered as a signal do nothing for the current message.
See <<aggregator>> for more information.