INT-3455 Orderly Shutdown Improvements

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

Do not stop schedulers and executors - allows mid-flow
QueueChannels to be drained.

Stop all inbound MessageProducers (that are not OrderlyShutdownCapable).

INT-3455 Polishing; PR Comments

Change deprecate method usage to the new version
This commit is contained in:
Gary Russell
2014-08-05 22:17:50 +03:00
committed by Artem Bilan
parent 5f214ea559
commit 6b8bdc7e5e
8 changed files with 162 additions and 230 deletions

View File

@@ -58,7 +58,6 @@ public class MBeanExporterParser extends AbstractSingleBeanDefinitionParser {
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "default-domain");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "object-name-static-properties");
IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "managed-components", "componentNamePatterns");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "shutdown-executor");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(builder, element, "object-naming-strategy", "namingStrategy");
builder.addPropertyValue("server", mbeanServer);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 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. You may obtain a copy of the License at
@@ -14,17 +14,12 @@
package org.springframework.integration.monitor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -56,12 +51,13 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.task.support.ExecutorServiceAdapter;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.history.MessageHistoryConfigurer;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.jmx.export.MBeanExporter;
@@ -76,10 +72,7 @@ import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.MetricType;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
@@ -115,7 +108,7 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
*/
@ManagedResource
public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostProcessor, BeanFactoryAware,
ApplicationContextAware, BeanClassLoaderAware, SmartLifecycle, Runnable {
ApplicationContextAware, BeanClassLoaderAware, SmartLifecycle {
private static final Log logger = LogFactory.getLog(IntegrationMBeanExporter.class);
@@ -135,6 +128,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
private final Set<SimpleMessageSourceMetrics> sources = new HashSet<SimpleMessageSourceMetrics>();
private final Set<Lifecycle> inboundLifecycleMessageProducers = new HashSet<Lifecycle>();
private final Set<DirectChannelMetrics> channels = new HashSet<DirectChannelMetrics>();
private final Map<String, Object> exposedBeans = new HashMap<String, Object>();
@@ -173,12 +168,8 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
private String[] componentNamePatterns = { "*" };
private volatile Executor shutdownExecutor;
private volatile long shutdownDeadline;
private volatile boolean shutdownForced;
private final AtomicBoolean shuttingDown = new AtomicBoolean();
private MessageHistoryConfigurer messageHistoryConfigurer;
@@ -236,11 +227,6 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
this.applicationContext = applicationContext;
}
public void setShutdownExecutor(Executor shutdownExecutor) {
Assert.notNull(shutdownExecutor, "Shutdown Executor may not be null");
this.shutdownExecutor = shutdownExecutor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
@@ -300,6 +286,13 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
bean = advised;
}
if (bean instanceof MessageProducer && bean instanceof Lifecycle) {
Lifecycle target = (Lifecycle) extractTarget(bean);
if (!(target instanceof AbstractReplyProducingMessageHandler)) { // TODO: change to AMPMH
this.inboundLifecycleMessageProducers.add(target);
}
}
return bean;
}
@@ -478,89 +471,66 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
/**
* Shutdown active components. If the thread calling this method is
* managed by a Spring-managed executor, you should provide a specific
* dedicated executor via the {@link #setShutdownExecutor}
* method. When this is provided, the shutdown will be performed on one
* of its threads, instead of the calling thread; thus avoiding
* the situation where we will wait for the current thread to terminate.
* <p> It is not necessary to supply this executor service if the
* current thread will not, itself, be shutdown as a result of
* calling this method.
* <p><b>Note:</b> The supplied executor service
* will <b>not</b> be shut down.
* Shutdown active components.
*
* @param force No longer used.
* @param howLong The time to wait in total for all activities to complete
* in milliseconds.
* @deprecated Use {@link #stopActiveComponents(long)}.
*/
@Deprecated
@ManagedOperation
public void stopActiveComponents(boolean force, long howLong) {
stopActiveComponents(howLong);
}
/**
* Shutdown active components.
*
* @param force If true, stop the executors with shutdownNow(), canceling
* running tasks. Overrides any settings on schedulers/executors. When true
* may result in error messages being sent to error channels.
* @param howLong The time to wait in total for all activities to complete
* in milliseconds.
*/
@ManagedOperation
public void stopActiveComponents(boolean force, long howLong) {
public void stopActiveComponents(long howLong) {
if (!this.shuttingDown.compareAndSet(false, true)) {
logger.error("Shutdown already in process");
return;
}
this.shutdownDeadline = System.currentTimeMillis() + howLong;
this.shutdownForced = force;
if (this.shutdownExecutor == null) {
try {
logger.debug("Running shutdown on current thread");
this.run();
} catch (Exception e) {
logger.error("Orderly shutdown failed", e);
}
try {
logger.debug("Running shutdown");
doShutdown();
}
else {
logger.debug("Launching shutdown on another thread");
this.shutdownExecutor.execute(this);
catch (Exception e) {
logger.error("Orderly shutdown failed", e);
}
}
/**
* Perform orderly shutdown - called or executed from
* {@link #stopActiveComponents(boolean, long)}.
* {@link #stopActiveComponents(long)}.
*/
@Override
public void run() {
private void doShutdown() {
try {
this.orderlyShutdownCapableComponentsBefore();
this.stopActiveChannels();
this.stopSchedulers();
if (System.currentTimeMillis() > this.shutdownDeadline) {
logger.error("Timed out before waiting for all schedulers to complete");
}
this.stopExecutors();
if (System.currentTimeMillis() > this.shutdownDeadline) {
logger.error("Timed out before waiting for all executors to complete");
}
this.stopNonSpringExecutors();
if (System.currentTimeMillis() > this.shutdownDeadline) {
logger.error("Timed out before waiting for all non-Spring executors to complete");
}
this.stopMessageSources();
orderlyShutdownCapableComponentsBefore();
stopActiveChannels();
stopMessageSources();
stopInboundMessageProducers();
// Wait any remaining time for messages to quiesce
long timeLeft = this.shutdownDeadline - System.currentTimeMillis();
long timeLeft = shutdownDeadline - System.currentTimeMillis();
if (timeLeft > 0) {
try {
Thread.sleep(timeLeft);
} catch (InterruptedException e) {
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Interrupted while waiting for quiesce");
}
this.orderlyShutdownCapableComponentsAfter();
}
else {
this.shutdownForced = true;
this.stopSchedulers();
this.stopExecutors();
this.stopNonSpringExecutors();
this.orderlyShutdownCapableComponentsAfter();
}
orderlyShutdownCapableComponentsAfter();
}
finally {
this.shuttingDown.set(false);
shuttingDown.set(false);
}
}
@@ -585,6 +555,22 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
}
/**
* Stops all inbound message producers (that are not {@link OrderlyShutdownCapable})
* - may cause interrupts.
*/
@ManagedOperation
public void stopInboundMessageProducers() {
for (Lifecycle producer : this.inboundLifecycleMessageProducers) {
if (!(producer instanceof OrderlyShutdownCapable)) {
if (logger.isInfoEnabled()) {
logger.info("Stopping message producer " + producer);
}
producer.stop();
}
}
}
@ManagedOperation
public void stopActiveChannels() {
// Stop any "active" channels (JMS etc).
@@ -600,80 +586,6 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
}
@ManagedOperation
public void stopSchedulers() {
if (logger.isDebugEnabled()) {
logger.debug("Stopping schedulers " + (this.shutdownForced ? "(force)" : ""));
}
List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
Map<String, ThreadPoolTaskScheduler> schedulers = this.applicationContext
.getBeansOfType(ThreadPoolTaskScheduler.class);
for (Entry<String, ThreadPoolTaskScheduler> entry : schedulers.entrySet()) {
ThreadPoolTaskScheduler scheduler = entry.getValue();
if (logger.isInfoEnabled()) {
logger.info("Stopping scheduler " + scheduler.getThreadNamePrefix());
}
ExecutorService executorService = scheduler.getScheduledExecutor();
executorServices.add(executorService);
doShutdownExecutorService(executorService);
}
waitForExecutors(executorServices);
logger.debug("Stopped schedulers");
}
@ManagedOperation
public void stopExecutors() {
if (logger.isDebugEnabled()) {
logger.debug("Stopping executors" + (this.shutdownForced ? "(force)" : ""));
}
List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
Map<String, ThreadPoolTaskExecutor> executors = this.applicationContext
.getBeansOfType(ThreadPoolTaskExecutor.class);
for (Entry<String, ThreadPoolTaskExecutor> entry : executors.entrySet()) {
ThreadPoolTaskExecutor executor = entry.getValue();
if (executor == this.shutdownExecutor) {
logger.debug("Skipping shutdown of shutdown executor");
}
else {
if (logger.isInfoEnabled()) {
logger.info("Stopping executor " + executor.getThreadNamePrefix());
}
ExecutorService executorService = executor.getThreadPoolExecutor();
executorServices.add(executorService);
doShutdownExecutorService(executorService);
}
}
waitForExecutors(executorServices);
logger.debug("Stopped executors");
}
@ManagedOperation
public void stopNonSpringExecutors() {
if (logger.isDebugEnabled()) {
logger.debug("Stopping other executors" + (this.shutdownForced ? "(force)" : ""));
}
List<ExecutorService> executorServices = new ArrayList<ExecutorService>();
Map<String, ExecutorService> nonSpringExecutors = this.applicationContext
.getBeansOfType(ExecutorService.class);
for (Entry<String, ExecutorService> entry : nonSpringExecutors.entrySet()) {
ExecutorService executorService = entry.getValue();
if (!(executorService instanceof ExecutorServiceAdapter)) {
if (logger.isInfoEnabled()) {
logger.info("Stopping executor service " + executorService);
}
executorServices.add(executorService);
doShutdownExecutorService(executorService);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring ExecutorServiceAdapter");
}
}
}
waitForExecutors(executorServices);
logger.debug("Stopped other executors");
}
protected final void orderlyShutdownCapableComponentsBefore() {
logger.debug("Initiating stop OrderlyShutdownCapable components");
Map<String, OrderlyShutdownCapable> components = this.applicationContext
@@ -793,33 +705,6 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
}
}
private void doShutdownExecutorService(ExecutorService executorService) {
if (this.shutdownForced) {
executorService.shutdownNow();
}
else {
executorService.shutdown();
}
}
private void waitForExecutors(List<ExecutorService> executorServices) {
for (ExecutorService executorService : executorServices) {
try {
if (!executorService.awaitTermination(this.shutdownDeadline
- System.currentTimeMillis(), TimeUnit.MILLISECONDS)) {
logger.error("Executor service " + executorService + " failed to terminate");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Interrupted while shutting down executor service " + executorService);
throw new MessagingException("Interrupted while shutting down", e);
}
if (System.currentTimeMillis() > this.shutdownDeadline) {
logger.error("Timed out before waiting for all executor services");
}
}
}
private void registerChannels() {
for (DirectChannelMetrics monitor : channels) {
String name = monitor.getName();
@@ -1231,4 +1116,5 @@ public class IntegrationMBeanExporter extends MBeanExporter implements BeanPostP
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, target);
}
}

View File

@@ -186,25 +186,6 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="shutdown-executor" use="optional">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.concurrent.Executor" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation>
An Executor used when shutting down the application using the 'stopActiveComponents()'
method. Only required when invoking the operation on a Spring-managed thread, such as
via a <control-bus/> from, say, an error flow. Using this executor avoids the
problem where the shutdown will wait for the current thread to terminate, time out,
and then force-close other components. When a dedicated executor is supplied,
the method will not wait on its threads to complete, and will terminate normally.
It is recommended that the executor used here is dedicated for this purpose and
not used elsewhere.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="object-naming-strategy" use="optional">
<xsd:annotation>
<xsd:appinfo>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2009-2012 the original author or authors.
* Copyright 2009-2014 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. You may obtain a copy of the License at
@@ -28,17 +28,22 @@ import javax.management.ObjectName;
import org.junit.After;
import org.junit.Test;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.context.OrderlyShutdownCapable;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.util.Assert;
public class MBeanExporterIntegrationTests {
@@ -100,7 +105,7 @@ public class MBeanExporterIntegrationTests {
assertNotNull(messageChannelsMonitor);
MBeanServer server = context.getBean(MBeanServer.class);
Set<ObjectName> names = server.queryNames(ObjectName.getInstance("org.springframework.integration:type=ManagedEndpoint,*"), null);
assertEquals(0, names.size());
assertEquals(2, names.size());
names = server.queryNames(ObjectName.getInstance("org.springframework.integration:name=explicit,*"), null);
assertEquals(1, names.size());
MBeanOperationInfo[] operations = server.getMBeanInfo(names.iterator().next()).getOperations();
@@ -114,13 +119,39 @@ public class MBeanExporterIntegrationTests {
// Lifecycle method name
assertEquals("start", startName);
assertTrue((Boolean) server.invoke(names.iterator().next(), "isRunning", null, null));
messageChannelsMonitor.stopActiveComponents(false, 3000);
messageChannelsMonitor.stopActiveComponents(3000);
assertFalse((Boolean) server.invoke(names.iterator().next(), "isRunning", null, null));
ActiveChannel activeChannel = context.getBean("activeChannel", ActiveChannel.class);
assertTrue(activeChannel.isStopCalled());
OtherActiveComponent otherActiveComponent = context.getBean(OtherActiveComponent.class);
assertTrue(otherActiveComponent.isBeforeCalled());
assertTrue(otherActiveComponent.isAfterCalled());
assertTrue(otherActiveComponent.isRunning());
assertFalse(context.getBean(AMessageProducer.class).isRunning());
// check pollers are still running
QueueChannel input = (QueueChannel) extractTarget(context.getBean("input"));
QueueChannel input2 = (QueueChannel) extractTarget(context.getBean("input2"));
input.purge(null);
input2.purge(null);
input.send(new GenericMessage<String>("foo"));
assertNotNull(input2.receive(10000));
}
private Object extractTarget(Object bean) {
if (!(bean instanceof Advised)) {
return bean;
}
Advised advised = (Advised) bean;
if (advised.getTargetSource() == null) {
return null;
}
try {
return extractTarget(advised.getTargetSource().getTarget());
}
catch (Exception e) {
return null;
}
}
@Test
@@ -214,15 +245,18 @@ public class MBeanExporterIntegrationTests {
// This has the potential to blow up if called before setDate().
// Depends on bean instantiation order and IntegrationMBeanExporter
// can influence that by aggressively instantiating other MBeanExporters
@Override
public Date getObject() throws Exception {
Assert.state(date != null, "A date must be provided");
return date;
}
@Override
public Class<?> getObjectType() {
return Date.class;
}
@Override
public boolean isSingleton() {
return true;
}
@@ -237,6 +271,7 @@ public class MBeanExporterIntegrationTests {
this.date = date;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(date != null, "A date must be provided");
}
@@ -246,21 +281,24 @@ public class MBeanExporterIntegrationTests {
public static class MetricFactoryBean implements FactoryBean<Metric> {
private MessageChannel channel;
private Metric date = new Metric();
private final Metric date = new Metric();
public void setChannel(MessageChannel channel) {
this.channel = channel;
}
@Override
public Metric getObject() throws Exception {
Assert.state(channel != null, "A channel must be provided");
return date;
}
@Override
public Class<?> getObjectType() {
return Metric.class;
}
@Override
public boolean isSingleton() {
return true;
}
@@ -280,6 +318,7 @@ public class MBeanExporterIntegrationTests {
this.channel = channel;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(channel != null, "A channel must be provided");
}
@@ -294,12 +333,14 @@ public class MBeanExporterIntegrationTests {
public static class SimpleService implements Service {
private int counter;
@Override
public String execute() throws Exception {
Thread.sleep(10L); // make the duration non-zero
counter++;
return "count="+counter;
}
@Override
public int getCounter() {
return counter;
}
@@ -313,31 +354,38 @@ public class MBeanExporterIntegrationTests {
private boolean stopCalled;
@Override
public boolean send(Message<?> message) {
return false;
}
@Override
public boolean send(Message<?> message, long timeout) {
return false;
}
@Override
public void start() {
}
@Override
public void stop() {
this.stopCalled = true;
}
@Override
public boolean isRunning() {
return false;
}
@Override
public boolean isStopCalled() {
return this.stopCalled;
}
}
public static class OtherActiveComponent implements OrderlyShutdownCapable {
public static class OtherActiveComponent extends MessageProducerSupport
implements OrderlyShutdownCapable {
private boolean beforeCalled;
@@ -351,14 +399,20 @@ public class MBeanExporterIntegrationTests {
return afterCalled;
}
@Override
public int beforeShutdown() {
this.beforeCalled = true;
return 0;
}
@Override
public int afterShutdown() {
this.afterCalled = true;
return 0;
}
}
public static class AMessageProducer extends MessageProducerSupport {
}
}

View File

@@ -15,6 +15,14 @@
<int:queue />
</int:channel>
<int:bridge input-channel="input" output-channel="input2">
<int:poller fixed-delay="200" />
</int:bridge>
<int:channel id="input2">
<int:queue />
</int:channel>
<int:inbound-channel-adapter id="explicit" ref="service" method="execute" channel="input">
<int:poller fixed-rate="200" />
</int:inbound-channel-adapter>
@@ -29,7 +37,13 @@
<bean id="nonSpringExecutor" class="java.util.concurrent.Executors" factory-method="newSingleThreadExecutor" />
<bean id="otherActiveComponent" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$OtherActiveComponent" />
<bean id="otherActiveComponent" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$OtherActiveComponent">
<property name="outputChannel" ref="input" />
</bean>
<bean id="messageProducer" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$AMessageProducer">
<property name="outputChannel" ref="input" />
</bean>
<bean id="ignoreWrappedExecutor" class="org.springframework.core.task.support.ExecutorServiceAdapter">
<constructor-arg ref="someExecutor" />

View File

@@ -18,15 +18,13 @@
<int:control-bus input-channel="toControlBus" />
<int-jmx:mbean-export id="integrationMbeanExporter" shutdown-executor="shutdownExec"
<int-jmx:mbean-export id="integrationMbeanExporter"
default-domain="self-destruct" />
<context:mbean-server />
<int:poller default="true" fixed-delay="500" />
<task:executor id="shutdownExec" />
<bean id="activeChannel" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$ActiveChannelImpl" />
<task:scheduler id="someScheduler" />
@@ -35,6 +33,8 @@
<bean id="nonSpringExecutor" class="java.util.concurrent.Executors" factory-method="newSingleThreadExecutor" />
<bean id="otherActiveComponent" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$OtherActiveComponent" />
<bean id="otherActiveComponent" class="org.springframework.integration.monitor.MBeanExporterIntegrationTests$OtherActiveComponent">
<property name="outputChannel" ref="nullChannel" />
</bean>
</beans>

View File

@@ -11,9 +11,8 @@
<para>
As described in <xref linkend="jmx-mbean-exporter"/>, the MBean exporter provides a JMX operation
<emphasis>stopActiveComponents</emphasis>, which is used to stop the application in an orderly manner. The operation
has two parameters, a boolean and a long. The boolean indicates whether attempts will be made
to stop (interrupt) active threads; in most cases this will be set to <emphasis>false</emphasis> for orderly
shutdown. The long parameter indicates how long (in milliseconds) the operation will wait to allow
has a single long parameter.
The parameter indicates how long (in milliseconds) the operation will wait to allow
in-flight messages to complete. The operation works as follows:
</para>
<para>
@@ -29,35 +28,21 @@
The second step stops any active channels, such as JMS- or AMQP-backed channels.
</para>
<para>
The third step stops all <classname>TaskScheduler</classname>s, preventing any new
scheduled operations (polling etc).
The third step stops all <classname>MessageSource</classname>s.
</para>
<para>
The fourth step stops all <classname>TaskExecutor</classname>s, preventing any new
tasks from running.
</para>
<note>
If the shutdown is running from a Spring-managed <classname>TaskExecutor</classname>, shutting down that
executor would cause all the timeout time to be consumed by this step, because the thread won't terminate).
For this reason, either use a dedicated executor (via the shutdownExecutor property on the MBean exporter),
or do not use a Spring-managed executor to invoke this operation.
</note>
<para>
The fifth step stops all <classname>MessageSource</classname>s.
The fourth step stops all inbound <classname>MessageProducer</classname>s (that are not
<interfacename>OrderlyShutdownCapable</interfacename>).
</para>
<para>
The sixth step waits for any remaining time left, as defined by the value of the long parameter passed
The fifth step waits for any remaining time left, as defined by the value of the long parameter passed
in to the operation. This is intended to allow any in-flight messages to complete their journeys. It is
therefore important to select an appropriate timeout when invoking this operation.
</para>
<para>
The seventh step calls <classname>afterShutdown()</classname> on all OrderlyShutdownCapable components.
The sixth step calls <classname>afterShutdown()</classname> on all OrderlyShutdownCapable components.
This allows such components to perform final shutdown tasks (closing all open sockets, for example).
</para>
<note>
If no time is left when we get to step 6, it probably means some thread is hung; in which case, the
operation attempts a forced shutdown on all schedulers and executors before exiting.
</note>
<para>
As discussed in <xref linkend="jmx-mbean-shutdown"/> this operation can be invoked using JMX. If you
wish to programmatically invoke the method, you will need to inject, or otherwise get a reference to,
@@ -76,4 +61,10 @@
<ulink url="https://github.com/spring-projects/spring-integration-samples/tree/master/intermediate/monitoring"
>monitoring Spring Integration sample application</ulink> for details.
</para>
<important>
The above algorithm was improved in <emphasis>version 4.1</emphasis>. Previously, all task executors
and schedulers were stopped. This could cause mid-flow messages in <classname>QueueChannel</classname>s
to remain. Now, the shutdown leaves pollers running in order to allow these messages to be
drained and processed.
</important>
</section>

View File

@@ -119,5 +119,12 @@
See <xref linkend="header-channel-registry"/> for more information.
</para>
</section>
<section id="4.1-orderly-shutdown">
<title>Orderly Shutdown</title>
<para>
Improvements have been made to the orderly shutdown algorithm.
See <xref linkend="jmx-shutdown"/> for more information.
</para>
</section>
</section>
</chapter>