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 */ 017package org.apache.camel.impl; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashSet; 024import java.util.LinkedHashMap; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.ExecutionException; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.Future; 033import java.util.concurrent.TimeUnit; 034import java.util.concurrent.atomic.AtomicBoolean; 035 036import org.apache.camel.CamelContext; 037import org.apache.camel.CamelContextAware; 038import org.apache.camel.Consumer; 039import org.apache.camel.Route; 040import org.apache.camel.Service; 041import org.apache.camel.ShutdownRoute; 042import org.apache.camel.ShutdownRunningTask; 043import org.apache.camel.Suspendable; 044import org.apache.camel.spi.InflightRepository; 045import org.apache.camel.spi.RouteStartupOrder; 046import org.apache.camel.spi.ShutdownAware; 047import org.apache.camel.spi.ShutdownPrepared; 048import org.apache.camel.spi.ShutdownStrategy; 049import org.apache.camel.support.ServiceSupport; 050import org.apache.camel.util.CollectionStringBuffer; 051import org.apache.camel.util.EventHelper; 052import org.apache.camel.util.ObjectHelper; 053import org.apache.camel.util.ServiceHelper; 054import org.apache.camel.util.StopWatch; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058/** 059 * Default {@link org.apache.camel.spi.ShutdownStrategy} which uses graceful shutdown. 060 * <p/> 061 * Graceful shutdown ensures that any inflight and pending messages will be taken into account 062 * and it will wait until these exchanges has been completed. 063 * <p/> 064 * This strategy will perform graceful shutdown in two steps: 065 * <ul> 066 * <li>Graceful - By suspending/stopping consumers, and let any in-flight exchanges complete</li> 067 * <li>Forced - After a given period of time, a timeout occurred and if there are still pending 068 * exchanges to complete, then a more aggressive forced strategy is performed.</li> 069 * </ul> 070 * The idea by the <tt>graceful</tt> shutdown strategy, is to stop taking in more new messages, 071 * and allow any existing inflight messages to complete. Then when there is no more inflight messages 072 * then the routes can be fully shutdown. This mean that if there is inflight messages then we will have 073 * to wait for these messages to complete. If they do not complete after a period of time, then a 074 * timeout triggers. And then a more aggressive strategy takes over. 075 * <p/> 076 * The idea by the <tt>forced</tt> shutdown strategy, is to stop continue processing messages. 077 * And force routes and its services to shutdown now. There is a risk when shutting down now, 078 * that some resources is not properly shutdown, which can cause side effects. The timeout value 079 * is by default 300 seconds, but can be customized. 080 * <p/> 081 * As this strategy will politely wait until all exchanges has been completed it can potential wait 082 * for a long time, and hence why a timeout value can be set. When the timeout triggers you can also 083 * specify whether the remainder consumers should be shutdown now or ignore. 084 * <p/> 085 * Will by default use a timeout of 300 seconds (5 minutes) by which it will shutdown now the remaining consumers. 086 * This ensures that when shutting down Camel it at some point eventually will shutdown. 087 * This behavior can of course be configured using the {@link #setTimeout(long)} and 088 * {@link #setShutdownNowOnTimeout(boolean)} methods. 089 * <p/> 090 * Routes will by default be shutdown in the reverse order of which they where started. 091 * You can customize this using the {@link #setShutdownRoutesInReverseOrder(boolean)} method. 092 * <p/> 093 * After route consumers have been shutdown, then any {@link ShutdownPrepared} services on the routes 094 * is being prepared for shutdown, by invoking {@link ShutdownPrepared#prepareShutdown(boolean,boolean)} which 095 * <tt>force=false</tt>. 096 * <p/> 097 * Then if a timeout occurred and the strategy has been configured with shutdown-now on timeout, then 098 * the strategy performs a more aggressive forced shutdown, by forcing all consumers to shutdown 099 * and then invokes {@link ShutdownPrepared#prepareShutdown(boolean,boolean)} with <tt>force=true</tt> 100 * on the services. This allows the services to know they should force shutdown now. 101 * <p/> 102 * When timeout occurred and a forced shutdown is happening, then there may be threads/tasks which are 103 * still inflight which may be rejected continued being routed. By default this can cause WARN and ERRORs 104 * to be logged. The option {@link #setSuppressLoggingOnTimeout(boolean)} can be used to suppress these 105 * logs, so they are logged at TRACE level instead. 106 * <p/> 107 * Also when a timeout occurred then information about the inflight exchanges is logged, if {@link #isLogInflightExchangesOnTimeout()} 108 * is enabled (is by default). This allows end users to known where these inflight exchanges currently are in the route(s), 109 * and how long time they have been inflight. 110 * <p/> 111 * This information can also be obtained from the {@link org.apache.camel.spi.InflightRepository} 112 * at all time during runtime. 113 * 114 * @version 115 */ 116public class DefaultShutdownStrategy extends ServiceSupport implements ShutdownStrategy, CamelContextAware { 117 private static final Logger LOG = LoggerFactory.getLogger(DefaultShutdownStrategy.class); 118 119 private CamelContext camelContext; 120 private ExecutorService executor; 121 private long timeout = 5 * 60; 122 private TimeUnit timeUnit = TimeUnit.SECONDS; 123 private boolean shutdownNowOnTimeout = true; 124 private boolean shutdownRoutesInReverseOrder = true; 125 private boolean suppressLoggingOnTimeout; 126 private boolean logInflightExchangesOnTimeout = true; 127 128 private volatile boolean forceShutdown; 129 private final AtomicBoolean timeoutOccurred = new AtomicBoolean(); 130 private volatile Future<?> currentShutdownTaskFuture; 131 132 public DefaultShutdownStrategy() { 133 } 134 135 public DefaultShutdownStrategy(CamelContext camelContext) { 136 this.camelContext = camelContext; 137 } 138 139 public void shutdown(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 140 shutdown(context, routes, getTimeout(), getTimeUnit()); 141 } 142 143 @Override 144 public void shutdownForced(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 145 doShutdown(context, routes, getTimeout(), getTimeUnit(), false, false, true); 146 } 147 148 public void suspend(CamelContext context, List<RouteStartupOrder> routes) throws Exception { 149 doShutdown(context, routes, getTimeout(), getTimeUnit(), true, false, false); 150 } 151 152 public void shutdown(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit) throws Exception { 153 doShutdown(context, routes, timeout, timeUnit, false, false, false); 154 } 155 156 public boolean shutdown(CamelContext context, RouteStartupOrder route, long timeout, TimeUnit timeUnit, boolean abortAfterTimeout) throws Exception { 157 List<RouteStartupOrder> routes = new ArrayList<RouteStartupOrder>(1); 158 routes.add(route); 159 return doShutdown(context, routes, timeout, timeUnit, false, abortAfterTimeout, false); 160 } 161 162 public void suspend(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit) throws Exception { 163 doShutdown(context, routes, timeout, timeUnit, true, false, false); 164 } 165 166 protected boolean doShutdown(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit, 167 boolean suspendOnly, boolean abortAfterTimeout, boolean forceShutdown) throws Exception { 168 169 // timeout must be a positive value 170 if (timeout <= 0) { 171 throw new IllegalArgumentException("Timeout must be a positive value"); 172 } 173 174 // just return if no routes to shutdown 175 if (routes.isEmpty()) { 176 return true; 177 } 178 179 StopWatch watch = new StopWatch(); 180 181 // at first sort according to route startup order 182 List<RouteStartupOrder> routesOrdered = new ArrayList<RouteStartupOrder>(routes); 183 routesOrdered.sort(new Comparator<RouteStartupOrder>() { 184 public int compare(RouteStartupOrder o1, RouteStartupOrder o2) { 185 return o1.getStartupOrder() - o2.getStartupOrder(); 186 } 187 }); 188 if (shutdownRoutesInReverseOrder) { 189 Collections.reverse(routesOrdered); 190 } 191 192 if (suspendOnly) { 193 LOG.info("Starting to graceful suspend " + routesOrdered.size() + " routes (timeout " + timeout + " " + timeUnit.toString().toLowerCase(Locale.ENGLISH) + ")"); 194 } else { 195 LOG.info("Starting to graceful shutdown " + routesOrdered.size() + " routes (timeout " + timeout + " " + timeUnit.toString().toLowerCase(Locale.ENGLISH) + ")"); 196 } 197 198 // use another thread to perform the shutdowns so we can support timeout 199 timeoutOccurred.set(false); 200 currentShutdownTaskFuture = getExecutorService().submit(new ShutdownTask(context, routesOrdered, timeout, timeUnit, suspendOnly, abortAfterTimeout, timeoutOccurred)); 201 try { 202 currentShutdownTaskFuture.get(timeout, timeUnit); 203 } catch (ExecutionException e) { 204 // unwrap execution exception 205 throw ObjectHelper.wrapRuntimeCamelException(e.getCause()); 206 } catch (Exception e) { 207 // either timeout or interrupted exception was thrown so this is okay 208 // as interrupted would mean cancel was called on the currentShutdownTaskFuture to signal a forced timeout 209 210 // we hit a timeout, so set the flag 211 timeoutOccurred.set(true); 212 213 // timeout then cancel the task 214 currentShutdownTaskFuture.cancel(true); 215 216 // signal we are forcing shutdown now, since timeout occurred 217 this.forceShutdown = forceShutdown; 218 219 // if set, stop processing and return false to indicate that the shutdown is aborting 220 if (!forceShutdown && abortAfterTimeout) { 221 LOG.warn("Timeout occurred during graceful shutdown. Aborting the shutdown now." 222 + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); 223 224 // we attempt to force shutdown so lets log the current inflight exchanges which are affected 225 logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); 226 227 return false; 228 } else { 229 if (forceShutdown || shutdownNowOnTimeout) { 230 LOG.warn("Timeout occurred during graceful shutdown. Forcing the routes to be shutdown now." 231 + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); 232 233 // we attempt to force shutdown so lets log the current inflight exchanges which are affected 234 logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); 235 236 // force the routes to shutdown now 237 shutdownRoutesNow(routesOrdered); 238 239 // now the route consumers has been shutdown, then prepare route services for shutdown now (forced) 240 for (RouteStartupOrder order : routes) { 241 for (Service service : order.getServices()) { 242 prepareShutdown(service, false, true, true, isSuppressLoggingOnTimeout()); 243 } 244 } 245 } else { 246 LOG.warn("Timeout occurred during graceful shutdown. Will ignore shutting down the remainder routes." 247 + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); 248 249 logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); 250 } 251 } 252 } finally { 253 currentShutdownTaskFuture = null; 254 } 255 256 // convert to seconds as its easier to read than a big milli seconds number 257 long seconds = TimeUnit.SECONDS.convert(watch.stop(), TimeUnit.MILLISECONDS); 258 259 LOG.info("Graceful shutdown of " + routesOrdered.size() + " routes completed in " + seconds + " seconds"); 260 return true; 261 } 262 263 @Override 264 public boolean forceShutdown(Service service) { 265 return forceShutdown; 266 } 267 268 @Override 269 public boolean hasTimeoutOccurred() { 270 return timeoutOccurred.get(); 271 } 272 273 public void setTimeout(long timeout) { 274 if (timeout <= 0) { 275 throw new IllegalArgumentException("Timeout must be a positive value"); 276 } 277 this.timeout = timeout; 278 } 279 280 public long getTimeout() { 281 return timeout; 282 } 283 284 public void setTimeUnit(TimeUnit timeUnit) { 285 this.timeUnit = timeUnit; 286 } 287 288 public TimeUnit getTimeUnit() { 289 return timeUnit; 290 } 291 292 public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) { 293 this.shutdownNowOnTimeout = shutdownNowOnTimeout; 294 } 295 296 public boolean isShutdownNowOnTimeout() { 297 return shutdownNowOnTimeout; 298 } 299 300 public boolean isShutdownRoutesInReverseOrder() { 301 return shutdownRoutesInReverseOrder; 302 } 303 304 public void setShutdownRoutesInReverseOrder(boolean shutdownRoutesInReverseOrder) { 305 this.shutdownRoutesInReverseOrder = shutdownRoutesInReverseOrder; 306 } 307 308 public boolean isSuppressLoggingOnTimeout() { 309 return suppressLoggingOnTimeout; 310 } 311 312 public void setSuppressLoggingOnTimeout(boolean suppressLoggingOnTimeout) { 313 this.suppressLoggingOnTimeout = suppressLoggingOnTimeout; 314 } 315 316 public boolean isLogInflightExchangesOnTimeout() { 317 return logInflightExchangesOnTimeout; 318 } 319 320 public void setLogInflightExchangesOnTimeout(boolean logInflightExchangesOnTimeout) { 321 this.logInflightExchangesOnTimeout = logInflightExchangesOnTimeout; 322 } 323 324 public CamelContext getCamelContext() { 325 return camelContext; 326 } 327 328 public void setCamelContext(CamelContext camelContext) { 329 this.camelContext = camelContext; 330 } 331 332 public Future<?> getCurrentShutdownTaskFuture() { 333 return currentShutdownTaskFuture; 334 } 335 336 /** 337 * Shutdown all the consumers immediately. 338 * 339 * @param routes the routes to shutdown 340 */ 341 protected void shutdownRoutesNow(List<RouteStartupOrder> routes) { 342 for (RouteStartupOrder order : routes) { 343 344 // set the route to shutdown as fast as possible by stopping after 345 // it has completed its current task 346 ShutdownRunningTask current = order.getRoute().getRouteContext().getShutdownRunningTask(); 347 if (current != ShutdownRunningTask.CompleteCurrentTaskOnly) { 348 LOG.debug("Changing shutdownRunningTask from {} to " + ShutdownRunningTask.CompleteCurrentTaskOnly 349 + " on route {} to shutdown faster", current, order.getRoute().getId()); 350 order.getRoute().getRouteContext().setShutdownRunningTask(ShutdownRunningTask.CompleteCurrentTaskOnly); 351 } 352 353 for (Consumer consumer : order.getInputs()) { 354 shutdownNow(consumer); 355 } 356 } 357 } 358 359 /** 360 * Shutdown all the consumers immediately. 361 * 362 * @param consumers the consumers to shutdown 363 */ 364 protected void shutdownNow(List<Consumer> consumers) { 365 for (Consumer consumer : consumers) { 366 shutdownNow(consumer); 367 } 368 } 369 370 /** 371 * Shutdown the consumer immediately. 372 * 373 * @param consumer the consumer to shutdown 374 */ 375 protected static void shutdownNow(Consumer consumer) { 376 LOG.trace("Shutting down: {}", consumer); 377 378 // allow us to do custom work before delegating to service helper 379 try { 380 ServiceHelper.stopService(consumer); 381 } catch (Throwable e) { 382 LOG.warn("Error occurred while shutting down route: " + consumer + ". This exception will be ignored.", e); 383 // fire event 384 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 385 } 386 387 LOG.trace("Shutdown complete for: {}", consumer); 388 } 389 390 /** 391 * Suspends/stops the consumer immediately. 392 * 393 * @param consumer the consumer to suspend 394 */ 395 protected static void suspendNow(Consumer consumer) { 396 LOG.trace("Suspending: {}", consumer); 397 398 // allow us to do custom work before delegating to service helper 399 try { 400 ServiceHelper.suspendService(consumer); 401 } catch (Throwable e) { 402 LOG.warn("Error occurred while suspending route: " + consumer + ". This exception will be ignored.", e); 403 // fire event 404 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 405 } 406 407 LOG.trace("Suspend complete for: {}", consumer); 408 } 409 410 private ExecutorService getExecutorService() { 411 if (executor == null) { 412 // use a thread pool that allow to terminate idle threads so they do not hang around forever 413 executor = camelContext.getExecutorServiceManager().newThreadPool(this, "ShutdownTask", 0, 1); 414 } 415 return executor; 416 } 417 418 @Override 419 protected void doStart() throws Exception { 420 ObjectHelper.notNull(camelContext, "CamelContext"); 421 // reset option 422 forceShutdown = false; 423 timeoutOccurred.set(false); 424 } 425 426 @Override 427 protected void doStop() throws Exception { 428 // noop 429 } 430 431 @Override 432 protected void doShutdown() throws Exception { 433 if (executor != null) { 434 // force shutting down as we are shutting down Camel 435 camelContext.getExecutorServiceManager().shutdownNow(executor); 436 // should clear executor so we can restart by creating a new thread pool 437 executor = null; 438 } 439 } 440 441 /** 442 * Prepares the services for shutdown, by invoking the {@link ShutdownPrepared#prepareShutdown(boolean, boolean)} method 443 * on the service if it implement this interface. 444 * 445 * @param service the service 446 * @param forced whether to force shutdown 447 * @param includeChildren whether to prepare the child of the service as well 448 */ 449 private static void prepareShutdown(Service service, boolean suspendOnly, boolean forced, boolean includeChildren, boolean suppressLogging) { 450 Set<Service> list; 451 if (includeChildren) { 452 // include error handlers as we want to prepare them for shutdown as well 453 list = ServiceHelper.getChildServices(service, true); 454 } else { 455 list = new LinkedHashSet<Service>(1); 456 list.add(service); 457 } 458 459 for (Service child : list) { 460 if (child instanceof ShutdownPrepared) { 461 try { 462 LOG.trace("Preparing {} shutdown on {}", forced ? "forced" : "", child); 463 ((ShutdownPrepared) child).prepareShutdown(suspendOnly, forced); 464 } catch (Exception e) { 465 if (suppressLogging) { 466 LOG.trace("Error during prepare shutdown on " + child + ". This exception will be ignored.", e); 467 } else { 468 LOG.warn("Error during prepare shutdown on " + child + ". This exception will be ignored.", e); 469 } 470 } 471 } 472 } 473 } 474 475 /** 476 * Holder for deferred consumers 477 */ 478 static class ShutdownDeferredConsumer { 479 private final Route route; 480 private final Consumer consumer; 481 482 ShutdownDeferredConsumer(Route route, Consumer consumer) { 483 this.route = route; 484 this.consumer = consumer; 485 } 486 487 Route getRoute() { 488 return route; 489 } 490 491 Consumer getConsumer() { 492 return consumer; 493 } 494 } 495 496 /** 497 * Shutdown task which shutdown all the routes in a graceful manner. 498 */ 499 static class ShutdownTask implements Runnable { 500 501 private final CamelContext context; 502 private final List<RouteStartupOrder> routes; 503 private final boolean suspendOnly; 504 private final boolean abortAfterTimeout; 505 private final long timeout; 506 private final TimeUnit timeUnit; 507 private final AtomicBoolean timeoutOccurred; 508 509 ShutdownTask(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit, 510 boolean suspendOnly, boolean abortAfterTimeout, AtomicBoolean timeoutOccurred) { 511 this.context = context; 512 this.routes = routes; 513 this.suspendOnly = suspendOnly; 514 this.abortAfterTimeout = abortAfterTimeout; 515 this.timeout = timeout; 516 this.timeUnit = timeUnit; 517 this.timeoutOccurred = timeoutOccurred; 518 } 519 520 public void run() { 521 // the strategy in this run method is to 522 // 1) go over the routes and shutdown those routes which can be shutdown asap 523 // some routes will be deferred to shutdown at the end, as they are needed 524 // by other routes so they can complete their tasks 525 // 2) wait until all inflight and pending exchanges has been completed 526 // 3) shutdown the deferred routes 527 528 LOG.debug("There are {} routes to {}", routes.size(), suspendOnly ? "suspend" : "shutdown"); 529 530 // list of deferred consumers to shutdown when all exchanges has been completed routed 531 // and thus there are no more inflight exchanges so they can be safely shutdown at that time 532 List<ShutdownDeferredConsumer> deferredConsumers = new ArrayList<ShutdownDeferredConsumer>(); 533 for (RouteStartupOrder order : routes) { 534 535 ShutdownRoute shutdownRoute = order.getRoute().getRouteContext().getShutdownRoute(); 536 ShutdownRunningTask shutdownRunningTask = order.getRoute().getRouteContext().getShutdownRunningTask(); 537 538 if (LOG.isTraceEnabled()) { 539 LOG.trace("{}{} with options [{},{}]", 540 new Object[]{suspendOnly ? "Suspending route: " : "Shutting down route: ", 541 order.getRoute().getId(), shutdownRoute, shutdownRunningTask}); 542 } 543 544 for (Consumer consumer : order.getInputs()) { 545 546 boolean suspend = false; 547 548 // assume we should shutdown if we are not deferred 549 boolean shutdown = shutdownRoute != ShutdownRoute.Defer; 550 551 if (shutdown) { 552 // if we are to shutdown then check whether we can suspend instead as its a more 553 // gentle way to graceful shutdown 554 555 // some consumers do not support shutting down so let them decide 556 // if a consumer is suspendable then prefer to use that and then shutdown later 557 if (consumer instanceof ShutdownAware) { 558 shutdown = !((ShutdownAware) consumer).deferShutdown(shutdownRunningTask); 559 } 560 if (shutdown && consumer instanceof Suspendable) { 561 // we prefer to suspend over shutdown 562 suspend = true; 563 } 564 } 565 566 // log at info level when a route has been shutdown (otherwise log at debug level to not be too noisy) 567 if (suspend) { 568 // only suspend it and then later shutdown it 569 suspendNow(consumer); 570 // add it to the deferred list so the route will be shutdown later 571 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 572 LOG.debug("Route: {} suspended and shutdown deferred, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 573 } else if (shutdown) { 574 shutdownNow(consumer); 575 LOG.info("Route: {} shutdown complete, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 576 } else { 577 // we will stop it later, but for now it must run to be able to help all inflight messages 578 // be safely completed 579 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 580 LOG.debug("Route: " + order.getRoute().getId() + (suspendOnly ? " shutdown deferred." : " suspension deferred.")); 581 } 582 } 583 } 584 585 // notify the services we intend to shutdown 586 for (RouteStartupOrder order : routes) { 587 for (Service service : order.getServices()) { 588 // skip the consumer as we handle that specially 589 if (service instanceof Consumer) { 590 continue; 591 } 592 prepareShutdown(service, suspendOnly, false, true, false); 593 } 594 } 595 596 // wait till there are no more pending and inflight messages 597 boolean done = false; 598 long loopDelaySeconds = 1; 599 long loopCount = 0; 600 while (!done && !timeoutOccurred.get()) { 601 int size = 0; 602 // number of inflights per route 603 final Map<String, Integer> routeInflight = new LinkedHashMap<String, Integer>(); 604 605 for (RouteStartupOrder order : routes) { 606 int inflight = context.getInflightRepository().size(order.getRoute().getId()); 607 inflight += getPendingInflightExchanges(order); 608 if (inflight > 0) { 609 String routeId = order.getRoute().getId(); 610 routeInflight.put(routeId, inflight); 611 size += inflight; 612 LOG.trace("{} inflight and pending exchanges for route: {}", inflight, routeId); 613 } 614 } 615 if (size > 0) { 616 try { 617 // build a message with inflight per route 618 CollectionStringBuffer csb = new CollectionStringBuffer(); 619 for (Map.Entry<String, Integer> entry : routeInflight.entrySet()) { 620 String row = String.format("%s = %s", entry.getKey(), entry.getValue()); 621 csb.append(row); 622 } 623 624 String msg = "Waiting as there are still " + size + " inflight and pending exchanges to complete, timeout in " 625 + (TimeUnit.SECONDS.convert(timeout, timeUnit) - (loopCount++ * loopDelaySeconds)) + " seconds."; 626 msg += " Inflights per route: [" + csb.toString() + "]"; 627 628 LOG.info(msg); 629 630 // log verbose if DEBUG logging is enabled 631 logInflightExchanges(context, routes, false); 632 633 Thread.sleep(loopDelaySeconds * 1000); 634 } catch (InterruptedException e) { 635 if (abortAfterTimeout) { 636 LOG.warn("Interrupted while waiting during graceful shutdown, will abort."); 637 return; 638 } else { 639 LOG.warn("Interrupted while waiting during graceful shutdown, will force shutdown now."); 640 break; 641 } 642 } 643 } else { 644 done = true; 645 } 646 } 647 648 // prepare for shutdown 649 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 650 Consumer consumer = deferred.getConsumer(); 651 if (consumer instanceof ShutdownAware) { 652 LOG.trace("Route: {} preparing to shutdown.", deferred.getRoute().getId()); 653 boolean forced = context.getShutdownStrategy().forceShutdown(consumer); 654 boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); 655 prepareShutdown(consumer, suspendOnly, forced, false, suppress); 656 LOG.debug("Route: {} preparing to shutdown complete.", deferred.getRoute().getId()); 657 } 658 } 659 660 // now all messages has been completed then stop the deferred consumers 661 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 662 Consumer consumer = deferred.getConsumer(); 663 if (suspendOnly) { 664 suspendNow(consumer); 665 LOG.info("Route: {} suspend complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 666 } else { 667 shutdownNow(consumer); 668 LOG.info("Route: {} shutdown complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 669 } 670 } 671 672 // now the route consumers has been shutdown, then prepare route services for shutdown 673 for (RouteStartupOrder order : routes) { 674 for (Service service : order.getServices()) { 675 boolean forced = context.getShutdownStrategy().forceShutdown(service); 676 boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); 677 prepareShutdown(service, suspendOnly, forced, true, suppress); 678 } 679 } 680 } 681 682 } 683 684 /** 685 * Calculates the total number of inflight exchanges for the given route 686 * 687 * @param order the route 688 * @return number of inflight exchanges 689 */ 690 protected static int getPendingInflightExchanges(RouteStartupOrder order) { 691 int inflight = 0; 692 693 // the consumer is the 1st service so we always get the consumer 694 // the child services are EIPs in the routes which may also have pending 695 // inflight exchanges (such as the aggregator) 696 for (Service service : order.getServices()) { 697 Set<Service> children = ServiceHelper.getChildServices(service); 698 for (Service child : children) { 699 if (child instanceof ShutdownAware) { 700 inflight += ((ShutdownAware) child).getPendingExchangesSize(); 701 } 702 } 703 } 704 705 return inflight; 706 } 707 708 /** 709 * Logs information about the inflight exchanges 710 * 711 * @param infoLevel <tt>true</tt> to log at INFO level, <tt>false</tt> to log at DEBUG level 712 */ 713 protected static void logInflightExchanges(CamelContext camelContext, List<RouteStartupOrder> routes, boolean infoLevel) { 714 // check if we need to log 715 if (!infoLevel && !LOG.isDebugEnabled()) { 716 return; 717 } 718 719 Collection<InflightRepository.InflightExchange> inflights = camelContext.getInflightRepository().browse(); 720 int size = inflights.size(); 721 if (size == 0) { 722 return; 723 } 724 725 // filter so inflight must start from any of the routes 726 Set<String> routeIds = new HashSet<String>(); 727 for (RouteStartupOrder route : routes) { 728 routeIds.add(route.getRoute().getId()); 729 } 730 Collection<InflightRepository.InflightExchange> filtered = new ArrayList<InflightRepository.InflightExchange>(); 731 for (InflightRepository.InflightExchange inflight : inflights) { 732 String routeId = inflight.getExchange().getFromRouteId(); 733 if (routeIds.contains(routeId)) { 734 filtered.add(inflight); 735 } 736 } 737 738 size = filtered.size(); 739 if (size == 0) { 740 return; 741 } 742 743 StringBuilder sb = new StringBuilder("There are " + size + " inflight exchanges:"); 744 for (InflightRepository.InflightExchange inflight : filtered) { 745 sb.append("\n\tInflightExchange: [exchangeId=").append(inflight.getExchange().getExchangeId()) 746 .append(", fromRouteId=").append(inflight.getExchange().getFromRouteId()) 747 .append(", routeId=").append(inflight.getRouteId()) 748 .append(", nodeId=").append(inflight.getNodeId()) 749 .append(", elapsed=").append(inflight.getElapsed()) 750 .append(", duration=").append(inflight.getDuration()) 751 .append("]"); 752 } 753 754 if (infoLevel) { 755 LOG.info(sb.toString()); 756 } else { 757 LOG.debug(sb.toString()); 758 } 759 } 760 761}