INT-2515 More Orderly Shutdown

* Add beginShutdown() and endShutdown() to OrderlyShutdownCapable
* JMS/AMQP stop listener containers
* TCP (server side)
** after beginShutdown() disallow new connections, drop (log) new messages
** after endShutdown() close server socket
* HTTP (server side)
** after beginShutdown() disallow any new requests (503 Service Unavailable)

* Docbook updates
** What's new section
** Orderly Shutdown section.
This commit is contained in:
Gary Russell
2012-06-19 15:21:53 -04:00
committed by Gunnar Hillert
parent da858b7451
commit ae0abc4f6c
17 changed files with 516 additions and 140 deletions

View File

@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -49,6 +50,7 @@ import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.MessagingException;
import org.springframework.integration.core.OrderlyShutdownCapable;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.http.converter.MultipartAwareFormHttpMessageConverter;
import org.springframework.integration.http.converter.SerializingHttpMessageConverter;
@@ -95,7 +97,8 @@ import org.springframework.web.util.UrlPathHelper;
* @author Gary Russell
* @since 2.0
*/
abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySupport {
abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySupport
implements OrderlyShutdownCapable {
private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder",
HttpRequestHandlingEndpointSupport.class.getClassLoader());
@@ -132,6 +135,10 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor
private volatile Map<String, Expression> headerExpressions;
private volatile boolean shuttingDown;
private final AtomicInteger activeCount = new AtomicInteger();
public HttpRequestHandlingEndpointSupport() {
this(true);
}
@@ -264,6 +271,10 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor
this.multipartResolver = multipartResolver;
}
protected boolean isShuttingDown() {
return this.shuttingDown;
}
@Override
public String getComponentType() {
return (this.expectReply) ? "http:inbound-gateway" : "http:inbound-channel-adapter";
@@ -298,13 +309,30 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor
this.validateSupportedMethods();
}
@Override
protected void doStart() {
this.shuttingDown = false;
super.doStart();
}
/**
* Handles the HTTP request by generating a Message and sending it to the request channel. If this gateway's
* 'expectReply' property is true, it will also generate a response from the reply Message once received.
* @return a the response Message
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final Message<?> doHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException {
if (this.isShuttingDown()) {
return createServiceUnavailableResponse();
}
else {
return actualDoHandleRequest(servletRequest, servletResponse);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private Message<?> actualDoHandleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException {
this.activeCount.incrementAndGet();
try {
ServletServerHttpRequest request = this.prepareRequest(servletRequest);
if (!this.supportedMethods.contains(request.getMethod())) {
@@ -386,9 +414,22 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor
}
finally {
this.postProcessRequest(servletRequest);
this.activeCount.decrementAndGet();
}
}
/**
* @return
*/
private Message<?> createServiceUnavailableResponse() {
if (logger.isDebugEnabled()) {
logger.debug("Endpoint is shutting down; returning status " + HttpStatus.SERVICE_UNAVAILABLE);
}
return MessageBuilder.withPayload("Endpoint is shutting down")
.setHeader(org.springframework.integration.http.HttpHeaders.STATUS_CODE, HttpStatus.SERVICE_UNAVAILABLE)
.build();
}
/**
* Converts the reply message to the appropriate HTTP reply object and
* sets up the servlet response.
@@ -521,4 +562,13 @@ abstract class HttpRequestHandlingEndpointSupport extends MessagingGatewaySuppor
}
}
}
public int beforeShutdown() {
this.shuttingDown = true;
return this.activeCount.get();
}
public int afterShutdown() {
return this.activeCount.get();
}
}

View File

@@ -23,9 +23,16 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.http.HttpStatus;
import org.springframework.integration.Message;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -246,5 +253,69 @@ public class HttpRequestHandlingControllerTests {
assertTrue("Wrong message: "+error, ((String)error.getArguments()[1]).startsWith("failed to send Message"));
}
@Test
public void shutDown() throws Exception {
DirectChannel requestChannel = new DirectChannel();
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
try {
latch2.countDown();
// hold up an active thread so we can verify the count and that it completes ok
latch1.await(10, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return requestMessage.getPayload().toString().toUpperCase();
}
};
requestChannel.subscribe(handler);
final HttpRequestHandlingController controller = new HttpRequestHandlingController(true);
controller.setRequestChannel(requestChannel);
controller.setViewName("foo");
final MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContent("hello".getBytes());
request.setContentType("text/plain");
MockHttpServletResponse response = new MockHttpServletResponse();
final AtomicInteger active = new AtomicInteger();
final AtomicBoolean expected503 = new AtomicBoolean();
Executors.newSingleThreadExecutor().execute(new Runnable() {
public void run() {
try {
// wait for the active thread
latch2.await(10, TimeUnit.SECONDS);
}
catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
// start the shutdown
active.set(controller.beforeShutdown());
try {
MockHttpServletResponse response = new MockHttpServletResponse();
controller.handleRequest(request, response);
expected503.set(response.getStatus() == HttpStatus.SERVICE_UNAVAILABLE.value());
latch1.countDown();
}
catch (Exception e) {
e.printStackTrace();
}
}
});
ModelAndView modelAndView = controller.handleRequest(request, response);
// verify we get a 503 after shutdown starts
assertEquals(1, active.get());
assertTrue(expected503.get());
// verify the active request still processed ok
assertEquals("foo", modelAndView.getViewName());
assertEquals(1, modelAndView.getModel().size());
Object reply = modelAndView.getModel().get("reply");
assertNotNull(reply);
assertEquals("HELLO", reply);
}
}