001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.impl;
018
019 import java.util.ArrayList;
020 import java.util.List;
021 import java.util.concurrent.ExecutionException;
022 import java.util.concurrent.ExecutorService;
023 import java.util.concurrent.Future;
024 import java.util.concurrent.TimeUnit;
025 import java.util.concurrent.TimeoutException;
026
027 import org.apache.camel.CamelContext;
028 import org.apache.camel.Consumer;
029 import org.apache.camel.Route;
030 import org.apache.camel.ShutdownRoute;
031 import org.apache.camel.ShutdownRunningTask;
032 import org.apache.camel.SuspendableService;
033 import org.apache.camel.spi.RouteStartupOrder;
034 import org.apache.camel.spi.ShutdownAware;
035 import org.apache.camel.spi.ShutdownStrategy;
036 import org.apache.camel.util.EventHelper;
037 import org.apache.camel.util.ObjectHelper;
038 import org.apache.camel.util.ServiceHelper;
039 import org.apache.camel.util.concurrent.ExecutorServiceHelper;
040 import org.apache.commons.logging.Log;
041 import org.apache.commons.logging.LogFactory;
042
043 /**
044 * Default {@link org.apache.camel.spi.ShutdownStrategy} which uses graceful shutdown.
045 * <p/>
046 * Graceful shutdown ensures that any inflight and pending messages will be taken into account
047 * and it will wait until these exchanges has been completed.
048 * <p/>
049 * As this strategy will politely wait until all exchanges has been completed it can potential wait
050 * for a long time, and hence why a timeout value can be set. When the timeout triggers you can also
051 * specify whether the remainder consumers should be shutdown now or ignore.
052 * <p/>
053 * Will by default use a timeout of 300 seconds (5 minutes) by which it will shutdown now the remaining consumers.
054 * This ensures that when shutting down Camel it at some point eventually will shutdown.
055 * This behavior can of course be configured using the {@link #setTimeout(long)} and
056 * {@link #setShutdownNowOnTimeout(boolean)} methods.
057 *
058 * @version $Revision: 894794 $
059 */
060 public class DefaultShutdownStrategy extends ServiceSupport implements ShutdownStrategy {
061 private static final transient Log LOG = LogFactory.getLog(DefaultShutdownStrategy.class);
062
063 private ExecutorService executor;
064 private long timeout = 5 * 60;
065 private TimeUnit timeUnit = TimeUnit.SECONDS;
066 private boolean shutdownNowOnTimeout = true;
067
068 public void shutdown(CamelContext context, List<RouteStartupOrder> routes) throws Exception {
069
070 long start = System.currentTimeMillis();
071
072 if (timeout > 0) {
073 LOG.info("Starting to graceful shutdown routes (timeout " + timeout + " " + timeUnit.toString().toLowerCase() + ")");
074 } else {
075 LOG.info("Starting to graceful shutdown routes (no timeout)");
076 }
077
078 // use another thread to perform the shutdowns so we can support timeout
079 Future future = getExecutorService().submit(new ShutdownTask(context, routes));
080 try {
081 if (timeout > 0) {
082 future.get(timeout, timeUnit);
083 } else {
084 future.get();
085 }
086 } catch (TimeoutException e) {
087 // timeout then cancel the task
088 future.cancel(true);
089
090 if (shutdownNowOnTimeout) {
091 LOG.warn("Timeout occurred. Now forcing all routes to be shutdown now.");
092 // force the routes to shutdown now
093 shutdownRoutesNow(routes);
094 } else {
095 LOG.warn("Timeout occurred. Will ignore shutting down the remainder route input consumers.");
096 }
097 } catch (ExecutionException e) {
098 // unwrap execution exception
099 throw ObjectHelper.wrapRuntimeCamelException(e.getCause());
100 }
101
102 long delta = System.currentTimeMillis() - start;
103 // convert to seconds as its easier to read than a big milli seconds number
104 long seconds = TimeUnit.SECONDS.convert(delta, TimeUnit.MILLISECONDS);
105
106 LOG.info("Graceful shutdown of routes completed in " + seconds + " seconds");
107 }
108
109 public void setTimeout(long timeout) {
110 this.timeout = timeout;
111 }
112
113 public long getTimeout() {
114 return timeout;
115 }
116
117 public void setTimeUnit(TimeUnit timeUnit) {
118 this.timeUnit = timeUnit;
119 }
120
121 public TimeUnit getTimeUnit() {
122 return timeUnit;
123 }
124
125 public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) {
126 this.shutdownNowOnTimeout = shutdownNowOnTimeout;
127 }
128
129 public boolean isShutdownNowOnTimeout() {
130 return shutdownNowOnTimeout;
131 }
132
133 /**
134 * Shutdown all the consumers immediately.
135 *
136 * @param routes the routes to shutdown
137 */
138 protected void shutdownRoutesNow(List<RouteStartupOrder> routes) {
139 for (RouteStartupOrder order : routes) {
140
141 // set the route to shutdown as fast as possible by stopping after
142 // it has completed its current task
143 ShutdownRunningTask current = order.getRoute().getRouteContext().getShutdownRunningTask();
144 if (current != ShutdownRunningTask.CompleteCurrentTaskOnly) {
145 LOG.info("Changing shutdownRunningTask from " + current + " to " + ShutdownRunningTask.CompleteCurrentTaskOnly
146 + " on route " + order.getRoute().getId() + " to shutdown faster");
147 order.getRoute().getRouteContext().setShutdownRunningTask(ShutdownRunningTask.CompleteCurrentTaskOnly);
148 }
149
150 for (Consumer consumer : order.getInputs()) {
151 shutdownNow(consumer);
152 }
153 }
154 }
155
156 /**
157 * Shutdown all the consumers immediately.
158 *
159 * @param consumers the consumers to shutdown
160 */
161 protected void shutdownNow(List<Consumer> consumers) {
162 for (Consumer consumer : consumers) {
163 shutdownNow(consumer);
164 }
165 }
166
167 /**
168 * Shutdown the consumer immediately.
169 *
170 * @param consumer the consumer to shutdown
171 */
172 protected void shutdownNow(Consumer consumer) {
173 if (LOG.isTraceEnabled()) {
174 LOG.trace("Shutting down: " + consumer);
175 }
176
177 // allow us to do custom work before delegating to service helper
178 try {
179 ServiceHelper.stopService(consumer);
180 } catch (Exception e) {
181 LOG.warn("Error occurred while shutting down route: " + consumer + ". This exception will be ignored.");
182 // fire event
183 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e);
184 }
185
186 if (LOG.isDebugEnabled()) {
187 LOG.debug("Shutdown complete for: " + consumer);
188 }
189 }
190
191 /**
192 * Suspends the consumer immediately.
193 *
194 * @param service the suspendable consumer
195 * @param consumer the consumer to suspend
196 */
197 protected void suspendNow(SuspendableService service, Consumer consumer) {
198 if (LOG.isTraceEnabled()) {
199 LOG.trace("Suspending: " + consumer);
200 }
201
202 try {
203 service.suspend();
204 } catch (Exception e) {
205 LOG.warn("Error occurred while suspending route: " + consumer + ". This exception will be ignored.");
206 // fire event
207 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e);
208 }
209
210 if (LOG.isDebugEnabled()) {
211 LOG.debug("Suspend complete for: " + consumer);
212 }
213 }
214
215 private ExecutorService getExecutorService() {
216 if (executor == null) {
217 executor = ExecutorServiceHelper.newSingleThreadExecutor("ShutdownTask", true);
218 }
219 return executor;
220 }
221
222 @Override
223 protected void doStart() throws Exception {
224 }
225
226 @Override
227 protected void doStop() throws Exception {
228 if (executor != null) {
229 executor.shutdownNow();
230 }
231 executor = null;
232 }
233
234 class ShutdownDeferredConsumer {
235 private final Route route;
236 private final Consumer consumer;
237
238 ShutdownDeferredConsumer(Route route, Consumer consumer) {
239 this.route = route;
240 this.consumer = consumer;
241 }
242
243 Route getRoute() {
244 return route;
245 }
246
247 Consumer getConsumer() {
248 return consumer;
249 }
250 }
251
252 /**
253 * Shutdown task which shutdown all the routes in a graceful manner.
254 */
255 class ShutdownTask implements Runnable {
256
257 private final CamelContext context;
258 private final List<RouteStartupOrder> routes;
259
260 public ShutdownTask(CamelContext context, List<RouteStartupOrder> routes) {
261 this.context = context;
262 this.routes = routes;
263 }
264
265 public void run() {
266 // the strategy in this run method is to
267 // 1) go over the routes and shutdown those routes which can be shutdown asap
268 // some routes will be deferred to shutdown at the end, as they are needed
269 // by other routes so they can complete their tasks
270 // 2) wait until all inflight and pending exchanges has been completed
271 // 3) shutdown the deferred routes
272
273 if (LOG.isDebugEnabled()) {
274 LOG.debug("There are " + routes.size() + " routes to shutdown");
275 }
276
277 // list of deferred consumers to shutdown when all exchanges has been completed routed
278 // and thus there are no more inflight exchanges so they can be safely shutdown at that time
279 List<ShutdownDeferredConsumer> deferredConsumers = new ArrayList<ShutdownDeferredConsumer>();
280
281 for (RouteStartupOrder order : routes) {
282
283 ShutdownRoute shutdownRoute = order.getRoute().getRouteContext().getShutdownRoute();
284 ShutdownRunningTask shutdownRunningTask = order.getRoute().getRouteContext().getShutdownRunningTask();
285
286 if (LOG.isTraceEnabled()) {
287 LOG.trace("Shutting down route: " + order.getRoute().getId() + " with options [" + shutdownRoute + "," + shutdownRunningTask + "]");
288 }
289
290 for (Consumer consumer : order.getInputs()) {
291
292 boolean suspend = false;
293
294 // assume we should shutdown if we are not deferred
295 boolean shutdown = shutdownRoute != ShutdownRoute.Defer;
296
297 if (shutdown) {
298 // if we are to shutdown then check whether we can suspend instead as its a more
299 // gentle wat to graceful shutdown
300
301 // some consumers do not support shutting down so let them decide
302 // if a consumer is suspendable then prefer to use that and then shutdown later
303 if (consumer instanceof ShutdownAware) {
304 shutdown = !((ShutdownAware) consumer).deferShutdown(shutdownRunningTask);
305 }
306 if (shutdown && consumer instanceof SuspendableService) {
307 // we prefer to suspend over shutdown
308 suspend = true;
309 }
310 }
311
312 if (suspend) {
313 // only suspend it and then later shutdown it
314 suspendNow((SuspendableService) consumer, consumer);
315 // add it to the deferred list so the route will be shutdown later
316 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer));
317 LOG.info("Route: " + order.getRoute().getId() + " suspended and shutdown deferred.");
318 } else if (shutdown) {
319 shutdownNow(consumer);
320 LOG.info("Route: " + order.getRoute().getId() + " shutdown complete.");
321 } else {
322 // we will stop it later, but for now it must run to be able to help all inflight messages
323 // be safely completed
324 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer));
325 LOG.info("Route: " + order.getRoute().getId() + " shutdown deferred.");
326 }
327 }
328 }
329
330 // wait till there are no more pending and inflight messages
331 boolean done = false;
332 while (!done) {
333 int size = 0;
334 for (RouteStartupOrder order : routes) {
335 for (Consumer consumer : order.getInputs()) {
336 int inflight = context.getInflightRepository().size(consumer.getEndpoint());
337 // include any additional pending exchanges on some consumers which may have internal
338 // memory queues such as seda
339 if (consumer instanceof ShutdownAware) {
340 inflight += ((ShutdownAware) consumer).getPendingExchangesSize();
341 }
342 if (inflight > 0) {
343 size += inflight;
344 if (LOG.isDebugEnabled()) {
345 LOG.debug(inflight + " inflight and pending exchanges for consumer: " + consumer);
346 }
347 }
348 }
349 }
350 if (size > 0) {
351 try {
352 LOG.info("Waiting as there are still " + size + " inflight and pending exchanges to complete before we can shutdown");
353 Thread.sleep(1000);
354 } catch (InterruptedException e) {
355 LOG.warn("Interrupted while waiting during graceful shutdown, will force shutdown now.");
356 Thread.currentThread().interrupt();
357 break;
358 }
359 } else {
360 done = true;
361 }
362 }
363
364 // now all messages has been completed then stop the deferred consumers
365 for (ShutdownDeferredConsumer deferred : deferredConsumers) {
366 shutdownNow(deferred.getConsumer());
367 LOG.info("Route: " + deferred.getRoute().getId() + " shutdown complete.");
368 }
369 }
370
371 }
372
373 }