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.Collections; 021 import java.util.Comparator; 022 import java.util.List; 023 import java.util.Locale; 024 import java.util.concurrent.ExecutionException; 025 import java.util.concurrent.ExecutorService; 026 import java.util.concurrent.Future; 027 import java.util.concurrent.TimeUnit; 028 import java.util.concurrent.TimeoutException; 029 030 import org.apache.camel.CamelContext; 031 import org.apache.camel.CamelContextAware; 032 import org.apache.camel.Consumer; 033 import org.apache.camel.Route; 034 import org.apache.camel.Service; 035 import org.apache.camel.ShutdownRoute; 036 import org.apache.camel.ShutdownRunningTask; 037 import org.apache.camel.SuspendableService; 038 import org.apache.camel.spi.RouteStartupOrder; 039 import org.apache.camel.spi.ShutdownAware; 040 import org.apache.camel.spi.ShutdownStrategy; 041 import org.apache.camel.support.ServiceSupport; 042 import org.apache.camel.util.EventHelper; 043 import org.apache.camel.util.ObjectHelper; 044 import org.apache.camel.util.ServiceHelper; 045 import org.apache.camel.util.StopWatch; 046 import org.slf4j.Logger; 047 import org.slf4j.LoggerFactory; 048 049 /** 050 * Default {@link org.apache.camel.spi.ShutdownStrategy} which uses graceful shutdown. 051 * <p/> 052 * Graceful shutdown ensures that any inflight and pending messages will be taken into account 053 * and it will wait until these exchanges has been completed. 054 * <p/> 055 * As this strategy will politely wait until all exchanges has been completed it can potential wait 056 * for a long time, and hence why a timeout value can be set. When the timeout triggers you can also 057 * specify whether the remainder consumers should be shutdown now or ignore. 058 * <p/> 059 * Will by default use a timeout of 300 seconds (5 minutes) by which it will shutdown now the remaining consumers. 060 * This ensures that when shutting down Camel it at some point eventually will shutdown. 061 * This behavior can of course be configured using the {@link #setTimeout(long)} and 062 * {@link #setShutdownNowOnTimeout(boolean)} methods. 063 * <p/> 064 * Routes will by default be shutdown in the reverse order of which they where started. 065 * You can customize this using the {@link #setShutdownRoutesInReverseOrder(boolean)} method. 066 * 067 * @version 068 */ 069 public class DefaultShutdownStrategy extends ServiceSupport implements ShutdownStrategy, CamelContextAware { 070 private static final transient Logger LOG = LoggerFactory.getLogger(DefaultShutdownStrategy.class); 071 072 private CamelContext camelContext; 073 private ExecutorService executor; 074 private long timeout = 5 * 60; 075 private TimeUnit timeUnit = TimeUnit.SECONDS; 076 private boolean shutdownNowOnTimeout = true; 077 private boolean shutdownRoutesInReverseOrder = true; 078 private volatile boolean forceShutdown; 079 080 public DefaultShutdownStrategy() { 081 } 082 083 public DefaultShutdownStrategy(CamelContext camelContext) { 084 this.camelContext = camelContext; 085 } 086 087 public void shutdown(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 088 shutdown(context, routes, getTimeout(), getTimeUnit()); 089 } 090 091 @Override 092 public void shutdownForced(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 093 doShutdown(context, routes, getTimeout(), getTimeUnit(), false, false, true); 094 } 095 096 public void suspend(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 097 doShutdown(context, routes, getTimeout(), getTimeUnit(), true, false, false); 098 } 099 100 public void shutdown(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit) throws Exception { 101 doShutdown(context, routes, timeout, timeUnit, false, false, false); 102 } 103 104 public boolean shutdown(CamelContext context, RouteStartupOrder route, long timeout, TimeUnit timeUnit, boolean abortAfterTimeout) throws Exception { 105 List<RouteStartupOrder> routes = new ArrayList<RouteStartupOrder>(1); 106 routes.add(route); 107 return doShutdown(context, routes, timeout, timeUnit, false, abortAfterTimeout, false); 108 } 109 110 public void suspend(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit) throws Exception { 111 doShutdown(context, routes, timeout, timeUnit, true, false, false); 112 } 113 114 protected boolean doShutdown(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit, 115 boolean suspendOnly, boolean abortAfterTimeout, boolean forceShutdown) throws Exception { 116 117 StopWatch watch = new StopWatch(); 118 119 // at first sort according to route startup order 120 List<RouteStartupOrder> routesOrdered = new ArrayList<RouteStartupOrder>(routes); 121 Collections.sort(routesOrdered, new Comparator<RouteStartupOrder>() { 122 public int compare(RouteStartupOrder o1, RouteStartupOrder o2) { 123 return o1.getStartupOrder() - o2.getStartupOrder(); 124 } 125 }); 126 if (shutdownRoutesInReverseOrder) { 127 Collections.reverse(routesOrdered); 128 } 129 130 if (timeout > 0) { 131 LOG.info("Starting to graceful shutdown " + routesOrdered.size() + " routes (timeout " + timeout + " " + timeUnit.toString().toLowerCase(Locale.ENGLISH) + ")"); 132 } else { 133 LOG.info("Starting to graceful shutdown " + routesOrdered.size() + " routes (no timeout)"); 134 } 135 136 // use another thread to perform the shutdowns so we can support timeout 137 Future future = getExecutorService().submit(new ShutdownTask(context, routesOrdered, timeout, timeUnit, suspendOnly, abortAfterTimeout)); 138 try { 139 if (timeout > 0) { 140 future.get(timeout, timeUnit); 141 } else { 142 future.get(); 143 } 144 } catch (TimeoutException e) { 145 // timeout then cancel the task 146 future.cancel(true); 147 148 // signal we are forcing shutdown now, since timeout occurred 149 this.forceShutdown = forceShutdown; 150 151 // if set, stop processing and return false to indicate that the shutdown is aborting 152 if (!forceShutdown && abortAfterTimeout) { 153 LOG.warn("Timeout occurred. Aborting the shutdown now."); 154 return false; 155 } else { 156 if (forceShutdown || shutdownNowOnTimeout) { 157 LOG.warn("Timeout occurred. Now forcing the routes to be shutdown now."); 158 // force the routes to shutdown now 159 shutdownRoutesNow(routesOrdered); 160 } else { 161 LOG.warn("Timeout occurred. Will ignore shutting down the remainder routes."); 162 } 163 } 164 } catch (ExecutionException e) { 165 // unwrap execution exception 166 throw ObjectHelper.wrapRuntimeCamelException(e.getCause()); 167 } 168 169 // convert to seconds as its easier to read than a big milli seconds number 170 long seconds = TimeUnit.SECONDS.convert(watch.stop(), TimeUnit.MILLISECONDS); 171 172 LOG.info("Graceful shutdown of " + routesOrdered.size() + " routes completed in " + seconds + " seconds"); 173 return true; 174 } 175 176 @Override 177 public boolean forceShutdown(Service service) { 178 return forceShutdown; 179 } 180 181 public void setTimeout(long timeout) { 182 this.timeout = timeout; 183 } 184 185 public long getTimeout() { 186 return timeout; 187 } 188 189 public void setTimeUnit(TimeUnit timeUnit) { 190 this.timeUnit = timeUnit; 191 } 192 193 public TimeUnit getTimeUnit() { 194 return timeUnit; 195 } 196 197 public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) { 198 this.shutdownNowOnTimeout = shutdownNowOnTimeout; 199 } 200 201 public boolean isShutdownNowOnTimeout() { 202 return shutdownNowOnTimeout; 203 } 204 205 public boolean isShutdownRoutesInReverseOrder() { 206 return shutdownRoutesInReverseOrder; 207 } 208 209 public void setShutdownRoutesInReverseOrder(boolean shutdownRoutesInReverseOrder) { 210 this.shutdownRoutesInReverseOrder = shutdownRoutesInReverseOrder; 211 } 212 213 public CamelContext getCamelContext() { 214 return camelContext; 215 } 216 217 public void setCamelContext(CamelContext camelContext) { 218 this.camelContext = camelContext; 219 } 220 221 /** 222 * Shutdown all the consumers immediately. 223 * 224 * @param routes the routes to shutdown 225 */ 226 protected void shutdownRoutesNow(List<RouteStartupOrder> routes) { 227 for (RouteStartupOrder order : routes) { 228 229 // set the route to shutdown as fast as possible by stopping after 230 // it has completed its current task 231 ShutdownRunningTask current = order.getRoute().getRouteContext().getShutdownRunningTask(); 232 if (current != ShutdownRunningTask.CompleteCurrentTaskOnly) { 233 LOG.debug("Changing shutdownRunningTask from {} to " + ShutdownRunningTask.CompleteCurrentTaskOnly 234 + " on route {} to shutdown faster", current, order.getRoute().getId()); 235 order.getRoute().getRouteContext().setShutdownRunningTask(ShutdownRunningTask.CompleteCurrentTaskOnly); 236 } 237 238 for (Consumer consumer : order.getInputs()) { 239 shutdownNow(consumer); 240 } 241 } 242 } 243 244 /** 245 * Shutdown all the consumers immediately. 246 * 247 * @param consumers the consumers to shutdown 248 */ 249 protected void shutdownNow(List<Consumer> consumers) { 250 for (Consumer consumer : consumers) { 251 shutdownNow(consumer); 252 } 253 } 254 255 /** 256 * Shutdown the consumer immediately. 257 * 258 * @param consumer the consumer to shutdown 259 */ 260 protected static void shutdownNow(Consumer consumer) { 261 LOG.trace("Shutting down: {}", consumer); 262 263 // allow us to do custom work before delegating to service helper 264 try { 265 ServiceHelper.stopService(consumer); 266 } catch (Throwable e) { 267 LOG.warn("Error occurred while shutting down route: " + consumer + ". This exception will be ignored.", e); 268 // fire event 269 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 270 } 271 272 LOG.trace("Shutdown complete for: {}", consumer); 273 } 274 275 /** 276 * Suspends/stops the consumer immediately. 277 * 278 * @param consumer the consumer to suspend 279 */ 280 protected static void suspendNow(Consumer consumer) { 281 LOG.trace("Suspending: {}", consumer); 282 283 // allow us to do custom work before delegating to service helper 284 try { 285 ServiceHelper.suspendService(consumer); 286 } catch (Throwable e) { 287 LOG.warn("Error occurred while suspending route: " + consumer + ". This exception will be ignored.", e); 288 // fire event 289 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 290 } 291 292 LOG.trace("Suspend complete for: {}", consumer); 293 } 294 295 private ExecutorService getExecutorService() { 296 if (executor == null) { 297 executor = camelContext.getExecutorServiceManager().newSingleThreadExecutor(this, "ShutdownTask"); 298 } 299 return executor; 300 } 301 302 @Override 303 protected void doStart() throws Exception { 304 ObjectHelper.notNull(camelContext, "CamelContext"); 305 // reset option 306 forceShutdown = false; 307 } 308 309 @Override 310 protected void doStop() throws Exception { 311 // noop 312 } 313 314 @Override 315 protected void doShutdown() throws Exception { 316 if (executor != null) { 317 camelContext.getExecutorServiceManager().shutdownNow(executor); 318 // should clear executor so we can restart by creating a new thread pool 319 executor = null; 320 } 321 } 322 323 static class ShutdownDeferredConsumer { 324 private final Route route; 325 private final Consumer consumer; 326 327 ShutdownDeferredConsumer(Route route, Consumer consumer) { 328 this.route = route; 329 this.consumer = consumer; 330 } 331 332 Route getRoute() { 333 return route; 334 } 335 336 Consumer getConsumer() { 337 return consumer; 338 } 339 } 340 341 /** 342 * Shutdown task which shutdown all the routes in a graceful manner. 343 */ 344 static class ShutdownTask implements Runnable { 345 346 private final CamelContext context; 347 private final List<RouteStartupOrder> routes; 348 private final boolean suspendOnly; 349 private final boolean abortAfterTimeout; 350 private final long timeout; 351 private final TimeUnit timeUnit; 352 353 public ShutdownTask(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit, 354 boolean suspendOnly, boolean abortAfterTimeout) { 355 this.context = context; 356 this.routes = routes; 357 this.suspendOnly = suspendOnly; 358 this.abortAfterTimeout = abortAfterTimeout; 359 this.timeout = timeout; 360 this.timeUnit = timeUnit; 361 } 362 363 public void run() { 364 // the strategy in this run method is to 365 // 1) go over the routes and shutdown those routes which can be shutdown asap 366 // some routes will be deferred to shutdown at the end, as they are needed 367 // by other routes so they can complete their tasks 368 // 2) wait until all inflight and pending exchanges has been completed 369 // 3) shutdown the deferred routes 370 371 LOG.debug("There are {} routes to {}", routes.size(), suspendOnly ? "suspend" : "shutdown"); 372 373 // list of deferred consumers to shutdown when all exchanges has been completed routed 374 // and thus there are no more inflight exchanges so they can be safely shutdown at that time 375 List<ShutdownDeferredConsumer> deferredConsumers = new ArrayList<ShutdownDeferredConsumer>(); 376 377 for (RouteStartupOrder order : routes) { 378 379 ShutdownRoute shutdownRoute = order.getRoute().getRouteContext().getShutdownRoute(); 380 ShutdownRunningTask shutdownRunningTask = order.getRoute().getRouteContext().getShutdownRunningTask(); 381 382 if (LOG.isTraceEnabled()) { 383 LOG.trace("{}{} with options [{},{}]", 384 new Object[]{suspendOnly ? "Suspending route: " : "Shutting down route: ", 385 order.getRoute().getId(), shutdownRoute, shutdownRunningTask}); 386 } 387 388 for (Consumer consumer : order.getInputs()) { 389 390 boolean suspend = false; 391 392 // assume we should shutdown if we are not deferred 393 boolean shutdown = shutdownRoute != ShutdownRoute.Defer; 394 395 if (shutdown) { 396 // if we are to shutdown then check whether we can suspend instead as its a more 397 // gentle way to graceful shutdown 398 399 // some consumers do not support shutting down so let them decide 400 // if a consumer is suspendable then prefer to use that and then shutdown later 401 if (consumer instanceof ShutdownAware) { 402 shutdown = !((ShutdownAware) consumer).deferShutdown(shutdownRunningTask); 403 } 404 if (shutdown && consumer instanceof SuspendableService) { 405 // we prefer to suspend over shutdown 406 suspend = true; 407 } 408 } 409 410 // log at info level when a route has been shutdown (otherwise log at debug level to not be too noisy) 411 if (suspend) { 412 // only suspend it and then later shutdown it 413 suspendNow(consumer); 414 // add it to the deferred list so the route will be shutdown later 415 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 416 LOG.debug("Route: {} suspended and shutdown deferred, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 417 } else if (shutdown) { 418 shutdownNow(consumer); 419 LOG.info("Route: {} shutdown complete, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 420 } else { 421 // we will stop it later, but for now it must run to be able to help all inflight messages 422 // be safely completed 423 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 424 LOG.debug("Route: " + order.getRoute().getId() + (suspendOnly ? " shutdown deferred." : " suspension deferred.")); 425 } 426 } 427 } 428 429 // wait till there are no more pending and inflight messages 430 boolean done = false; 431 long loopDelaySeconds = 1; 432 long loopCount = 0; 433 while (!done) { 434 int size = 0; 435 for (RouteStartupOrder order : routes) { 436 int inflight = context.getInflightRepository().size(order.getRoute().getId()); 437 for (Consumer consumer : order.getInputs()) { 438 // include any additional pending exchanges on some consumers which may have internal 439 // memory queues such as seda 440 if (consumer instanceof ShutdownAware) { 441 inflight += ((ShutdownAware) consumer).getPendingExchangesSize(); 442 } 443 } 444 if (inflight > 0) { 445 size += inflight; 446 LOG.trace("{} inflight and pending exchanges for route: {}", inflight, order.getRoute().getId()); 447 } 448 } 449 if (size > 0) { 450 try { 451 LOG.info("Waiting as there are still " + size + " inflight and pending exchanges to complete, timeout in " 452 + (TimeUnit.SECONDS.convert(timeout, timeUnit) - (loopCount++ * loopDelaySeconds)) + " seconds."); 453 Thread.sleep(loopDelaySeconds * 1000); 454 } catch (InterruptedException e) { 455 if (abortAfterTimeout) { 456 LOG.warn("Interrupted while waiting during graceful shutdown, will abort."); 457 return; 458 } else { 459 LOG.warn("Interrupted while waiting during graceful shutdown, will force shutdown now."); 460 break; 461 } 462 } 463 } else { 464 done = true; 465 } 466 } 467 468 // prepare for shutdown 469 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 470 Consumer consumer = deferred.getConsumer(); 471 if (consumer instanceof ShutdownAware) { 472 LOG.trace("Route: {} preparing to shutdown.", deferred.getRoute().getId()); 473 ((ShutdownAware) consumer).prepareShutdown(); 474 LOG.debug("Route: {} preparing to shutdown complete.", deferred.getRoute().getId()); 475 } 476 } 477 478 // now all messages has been completed then stop the deferred consumers 479 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 480 Consumer consumer = deferred.getConsumer(); 481 if (suspendOnly) { 482 suspendNow(consumer); 483 LOG.info("Route: {} suspend complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 484 } else { 485 shutdownNow(consumer); 486 LOG.info("Route: {} shutdown complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 487 } 488 } 489 } 490 491 } 492 493 }