INT-3004 Detect Timeout in MessagingTemplate
https://jira.springsource.org/browse/INT-3004 Previously, reply messages were silently ignored if the request thread times out or caught an exception, or had already handled a reply. Add a WARN log if the client times out or catches an exception, or has already received a reply. Optionally make these fatal; at this time, we are not exposing this boolean on the namespaces see INT-3005. Polishing - PR Comments INT-3004 Polishing Change MessagingTemplate property to throwExceptionOnLateReply.
This commit is contained in:
committed by
Mark Fisher
parent
698cb10fe4
commit
91f040f1c7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -41,9 +42,10 @@ import org.springframework.util.Assert;
|
||||
* This is the central class for invoking message exchange operations across
|
||||
* {@link MessageChannel}s. It supports one-way send and receive calls as well
|
||||
* as request/reply.
|
||||
*
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class MessagingTemplate implements MessagingOperations, BeanFactoryAware, InitializingBean {
|
||||
|
||||
@@ -63,6 +65,8 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
|
||||
private final Object initializationMonitor = new Object();
|
||||
|
||||
private volatile boolean throwExceptionOnLateReply = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create a MessagingTemplate with no default channel. Note, that one
|
||||
@@ -110,7 +114,7 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
|
||||
/**
|
||||
* Specify the timeout value to use for send operations.
|
||||
*
|
||||
*
|
||||
* @param sendTimeout the send timeout in milliseconds
|
||||
*/
|
||||
public void setSendTimeout(long sendTimeout) {
|
||||
@@ -119,7 +123,7 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
|
||||
/**
|
||||
* Specify the timeout value to use for receive operations.
|
||||
*
|
||||
*
|
||||
* @param receiveTimeout the receive timeout in milliseconds
|
||||
*/
|
||||
public void setReceiveTimeout(long receiveTimeout) {
|
||||
@@ -132,6 +136,18 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether or not an attempt to send on the reply channel throws an exception
|
||||
* if no receiving thread will actually receive the reply. This can occur
|
||||
* if the receiving thread has already timed out, or will never call receive()
|
||||
* because it caught an exception, or has already received a reply.
|
||||
* (default false - just a WARN log is emitted in these cases).
|
||||
* @param throwExceptionOnLateReply TRUE or FALSE.
|
||||
*/
|
||||
public void setThrowExceptionOnLateReply(boolean throwExceptionOnLateReply) {
|
||||
this.throwExceptionOnLateReply = throwExceptionOnLateReply;
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() {
|
||||
synchronized (this.initializationMonitor) {
|
||||
if (this.initialized) {
|
||||
@@ -310,12 +326,18 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
private <S, R> Message<R> doSendAndReceive(MessageChannel channel, Message<S> requestMessage) {
|
||||
Object originalReplyChannelHeader = requestMessage.getHeaders().getReplyChannel();
|
||||
Object originalErrorChannelHeader = requestMessage.getHeaders().getErrorChannel();
|
||||
TemporaryReplyChannel replyChannel = new TemporaryReplyChannel(this.receiveTimeout);
|
||||
TemporaryReplyChannel replyChannel = new TemporaryReplyChannel(this.receiveTimeout, this.throwExceptionOnLateReply);
|
||||
requestMessage = MessageBuilder.fromMessage(requestMessage)
|
||||
.setReplyChannel(replyChannel)
|
||||
.setErrorChannel(replyChannel)
|
||||
.build();
|
||||
this.doSend(channel, requestMessage);
|
||||
try {
|
||||
this.doSend(channel, requestMessage);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
replyChannel.setClientWontReceive(true);
|
||||
throw e;
|
||||
}
|
||||
Message<R> reply = this.doReceive(replyChannel);
|
||||
if (reply != null) {
|
||||
reply = MessageBuilder.fromMessage(reply)
|
||||
@@ -356,15 +378,30 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
|
||||
private static class TemporaryReplyChannel implements PollableChannel {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TemporaryReplyChannel.class);
|
||||
|
||||
private volatile Message<?> message;
|
||||
|
||||
private final long receiveTimeout;
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
private final boolean throwExceptionOnLateReply;
|
||||
|
||||
public TemporaryReplyChannel(long receiveTimeout) {
|
||||
private volatile boolean clientTimedOut;
|
||||
|
||||
private volatile boolean clientWontReceive;
|
||||
|
||||
private volatile boolean clientHasReceived;
|
||||
|
||||
|
||||
public TemporaryReplyChannel(long receiveTimeout, boolean throwExceptionOnLateReply) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
this.throwExceptionOnLateReply = throwExceptionOnLateReply;
|
||||
}
|
||||
|
||||
public void setClientWontReceive(boolean clientWontReceive) {
|
||||
this.clientWontReceive = clientWontReceive;
|
||||
}
|
||||
|
||||
|
||||
@@ -376,9 +413,15 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
try {
|
||||
if (this.receiveTimeout < 0) {
|
||||
this.latch.await();
|
||||
this.clientHasReceived = true;
|
||||
}
|
||||
else {
|
||||
this.latch.await(this.receiveTimeout, TimeUnit.MILLISECONDS);
|
||||
if (this.latch.await(this.receiveTimeout, TimeUnit.MILLISECONDS)) {
|
||||
this.clientHasReceived = true;
|
||||
}
|
||||
else {
|
||||
this.clientTimedOut = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
@@ -394,6 +437,25 @@ public class MessagingTemplate implements MessagingOperations, BeanFactoryAware,
|
||||
public boolean send(Message<?> message, long timeout) {
|
||||
this.message = message;
|
||||
this.latch.countDown();
|
||||
if (this.clientTimedOut || this.clientHasReceived || this.clientWontReceive) {
|
||||
String exceptionMessage = "";
|
||||
if (this.clientTimedOut) {
|
||||
exceptionMessage = "Reply message being sent, but the receiving thread has already timed out";
|
||||
}
|
||||
else if (this.clientHasReceived) {
|
||||
exceptionMessage = "Reply message being sent, but the receiving thread has already received a reply";
|
||||
}
|
||||
else if (this.clientWontReceive) {
|
||||
exceptionMessage = "Reply message being sent, but the receiving thread has already caught an exception and won't receive";
|
||||
}
|
||||
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn(exceptionMessage + ":" + message);
|
||||
}
|
||||
if (this.throwExceptionOnLateReply) {
|
||||
throw new MessageDeliveryException(message, exceptionMessage);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
@@ -20,11 +20,13 @@ 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 static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -34,6 +36,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.integration.Message;
|
||||
import org.springframework.integration.MessageChannel;
|
||||
import org.springframework.integration.MessagingException;
|
||||
import org.springframework.integration.channel.AbstractMessageChannel;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
@@ -51,10 +54,11 @@ import org.springframework.integration.test.util.TestUtils.TestApplicationContex
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class MessagingTemplateTests {
|
||||
|
||||
private TestApplicationContext context = TestUtils.createTestApplicationContext();
|
||||
private final TestApplicationContext context = TestUtils.createTestApplicationContext();
|
||||
|
||||
private QueueChannel requestChannel;
|
||||
|
||||
@@ -305,16 +309,16 @@ public class MessagingTemplateTests {
|
||||
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("testChannel", testChannel);
|
||||
|
||||
|
||||
MessagingTemplate template = new MessagingTemplate();
|
||||
template.setBeanFactory(beanFactory);
|
||||
|
||||
|
||||
template.afterPropertiesSet();
|
||||
Message<?> message = MessageBuilder.withPayload("test").build();
|
||||
template.send("testChannel", message);
|
||||
assertEquals(message, testChannel.receive(0));
|
||||
|
||||
template.setChannelResolver(new ChannelResolver() {
|
||||
|
||||
template.setChannelResolver(new ChannelResolver() {
|
||||
public MessageChannel resolveChannelName(String channelName) {
|
||||
return anotherChannel;
|
||||
}
|
||||
@@ -405,7 +409,7 @@ public class MessagingTemplateTests {
|
||||
template.convertAndSend(channel, "test");
|
||||
Message<?> reply = channel.receive(0);
|
||||
assertNotNull(reply);
|
||||
assertEquals("test", reply.getPayload());
|
||||
assertEquals("test", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,7 +456,7 @@ public class MessagingTemplateTests {
|
||||
template.convertAndSend(channel, "test");
|
||||
Message<?> reply = channel.receive(0);
|
||||
assertNotNull(reply);
|
||||
assertEquals("to:test", reply.getPayload());
|
||||
assertEquals("to:test", reply.getPayload());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -524,6 +528,86 @@ public class MessagingTemplateTests {
|
||||
assertEquals("from:TO:TEST", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLateReply() {
|
||||
MessagingTemplate template = new MessagingTemplate();
|
||||
QueueChannel channel = new QueueChannel();
|
||||
template.setDefaultChannel(channel);
|
||||
template.setReceiveTimeout(1);
|
||||
template.setThrowExceptionOnLateReply(true);
|
||||
Object result = template.sendAndReceive(new GenericMessage<String>("foo"));
|
||||
assertNull(result);
|
||||
Message<?> message = channel.receive();
|
||||
try {
|
||||
((MessageChannel) message.getHeaders().getReplyChannel()).send(new GenericMessage<String>("bar"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertEquals("Reply message being sent, but the receiving thread has already timed out", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeverReceive() {
|
||||
MessagingTemplate template = new MessagingTemplate();
|
||||
DirectChannel channel = new DirectChannel();
|
||||
final AtomicReference<MessageChannel> replyChannel = new AtomicReference<MessageChannel>();
|
||||
channel.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
replyChannel.set((MessageChannel) message.getHeaders().getReplyChannel());
|
||||
throw new MessagingException("foo");
|
||||
}
|
||||
});
|
||||
template.setDefaultChannel(channel);
|
||||
template.setReceiveTimeout(10000);
|
||||
template.setThrowExceptionOnLateReply(true);
|
||||
try {
|
||||
template.sendAndReceive(new GenericMessage<String>("foo"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertTrue(e.getMessage().equals("foo"));
|
||||
}
|
||||
try {
|
||||
replyChannel.get().send(new GenericMessage<String>("bar"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertEquals(
|
||||
"Reply message being sent, but the receiving thread has already caught an exception and won't receive",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoReplies() {
|
||||
MessagingTemplate template = new MessagingTemplate();
|
||||
DirectChannel channel = new DirectChannel();
|
||||
final AtomicReference<MessageChannel> replyChannel = new AtomicReference<MessageChannel>();
|
||||
channel.subscribe(new MessageHandler() {
|
||||
@Override
|
||||
public void handleMessage(Message<?> message) throws MessagingException {
|
||||
MessageChannel replyChannelHeader = (MessageChannel) message.getHeaders().getReplyChannel();
|
||||
replyChannel.set(replyChannelHeader);
|
||||
replyChannelHeader.send(new GenericMessage<String>("bar"));
|
||||
}
|
||||
});
|
||||
template.setDefaultChannel(channel);
|
||||
template.setReceiveTimeout(10000);
|
||||
template.setThrowExceptionOnLateReply(true);
|
||||
Message<?> reply = template.sendAndReceive(new GenericMessage<String>("foo"));
|
||||
assertTrue(reply.getPayload().equals("bar"));
|
||||
try {
|
||||
replyChannel.get().send(new GenericMessage<String>("baz"));
|
||||
fail("Exception expected");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
assertEquals(
|
||||
"Reply message being sent, but the receiving thread has already received a reply",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestMapper implements InboundMessageMapper<Object>, OutboundMessageMapper<Object> {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user