INT-2937 Fix Race Condition in MGS Reaper

Groups changed within the same millisecond that they
were selected for reaping can cause aggregators to
emit duplicate messages.

Use the re-fetched group (used to see if the timetamp
changed) instead of the group passed in to forceClose
(the group might have changed since it was selected as
eligible for reaping).

We still retain the last modified check because it
might have been expired using timeoutOnIdle.
This commit is contained in:
Gary Russell
2013-02-17 14:46:12 -05:00
committed by Mark Fisher
parent 69fcf5b16a
commit eb07178381
2 changed files with 46 additions and 11 deletions

View File

@@ -16,9 +16,11 @@
package org.springframework.integration.aggregator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -28,9 +30,11 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.SimpleMessageStore;
@@ -249,4 +253,34 @@ public class AbstractCorrelatingMessageHandlerTests {
assertEquals(0, TestUtils.getPropertyValue(handler, "messageStore.groupIdToMessageGroup", Map.class).size());
}
@Test
public void testReapWithChangeInSameMillisecond() throws Exception {
MessageGroupProcessor mgp = new DefaultAggregatingMessageGroupProcessor();
AggregatingMessageHandler handler = new AggregatingMessageHandler(mgp);
handler.setReleaseStrategy(new ReleaseStrategy() {
public boolean canRelease(MessageGroup group) {
return true;
}
});
QueueChannel outputChannel = new QueueChannel();
handler.setOutputChannel(outputChannel);
MessageGroupStore mgs = TestUtils.getPropertyValue(handler, "messageStore", MessageGroupStore.class);
Method forceComplete = AbstractCorrelatingMessageHandler.class.getDeclaredMethod("forceComplete", MessageGroup.class);
forceComplete.setAccessible(true);
mgs.addMessageToGroup("foo", new GenericMessage<String>("foo"));
GenericMessage<String> secondMessage = new GenericMessage<String>("bar");
mgs.addMessageToGroup("foo", secondMessage);
MessageGroup group = mgs.getMessageGroup("foo");
// remove a message
mgs.removeMessageFromGroup("foo", secondMessage);
// force lastModified to be the same
MessageGroup groupNow = mgs.getMessageGroup("foo");
new DirectFieldAccessor(group).setPropertyValue("lastModified", groupNow.getLastModified());
forceComplete.invoke(handler, group);
Message<?> message = outputChannel.receive(0);
assertNotNull(message);
Collection<?> payload = (Collection<?>) message.getPayload();
assertEquals(1, payload.size());
}
}