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.SuspendableService; 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)} 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)} 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 // just return if no routes to shutdown 170 if (routes.isEmpty()) { 171 return true; 172 } 173 174 StopWatch watch = new StopWatch(); 175 176 // at first sort according to route startup order 177 List<RouteStartupOrder> routesOrdered = new ArrayList<RouteStartupOrder>(routes); 178 Collections.sort(routesOrdered, new Comparator<RouteStartupOrder>() { 179 public int compare(RouteStartupOrder o1, RouteStartupOrder o2) { 180 return o1.getStartupOrder() - o2.getStartupOrder(); 181 } 182 }); 183 if (shutdownRoutesInReverseOrder) { 184 Collections.reverse(routesOrdered); 185 } 186 187 LOG.info("Starting to graceful shutdown " + routesOrdered.size() + " routes (timeout " + timeout + " " + timeUnit.toString().toLowerCase(Locale.ENGLISH) + ")"); 188 189 // use another thread to perform the shutdowns so we can support timeout 190 timeoutOccurred.set(false); 191 currentShutdownTaskFuture = getExecutorService().submit(new ShutdownTask(context, routesOrdered, timeout, timeUnit, suspendOnly, abortAfterTimeout, timeoutOccurred)); 192 try { 193 currentShutdownTaskFuture.get(timeout, timeUnit); 194 } catch (ExecutionException e) { 195 // unwrap execution exception 196 throw ObjectHelper.wrapRuntimeCamelException(e.getCause()); 197 } catch (Exception e) { 198 // either timeout or interrupted exception was thrown so this is okay 199 // as interrupted would mean cancel was called on the currentShutdownTaskFuture to signal a forced timeout 200 201 // we hit a timeout, so set the flag 202 timeoutOccurred.set(true); 203 204 // timeout then cancel the task 205 currentShutdownTaskFuture.cancel(true); 206 207 // signal we are forcing shutdown now, since timeout occurred 208 this.forceShutdown = forceShutdown; 209 210 // if set, stop processing and return false to indicate that the shutdown is aborting 211 if (!forceShutdown && abortAfterTimeout) { 212 LOG.warn("Timeout occurred during graceful shutdown. Aborting the shutdown now." 213 + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); 214 215 // we attempt to force shutdown so lets log the current inflight exchanges which are affected 216 logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); 217 218 return false; 219 } else { 220 if (forceShutdown || shutdownNowOnTimeout) { 221 LOG.warn("Timeout occurred during graceful shutdown. Forcing the routes to be 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 // force the routes to shutdown now 228 shutdownRoutesNow(routesOrdered); 229 230 // now the route consumers has been shutdown, then prepare route services for shutdown now (forced) 231 for (RouteStartupOrder order : routes) { 232 for (Service service : order.getServices()) { 233 prepareShutdown(service, true, true, isSuppressLoggingOnTimeout()); 234 } 235 } 236 } else { 237 LOG.warn("Timeout occurred during graceful shutdown. Will ignore shutting down the remainder routes." 238 + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); 239 240 logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); 241 } 242 } 243 } finally { 244 currentShutdownTaskFuture = null; 245 } 246 247 // convert to seconds as its easier to read than a big milli seconds number 248 long seconds = TimeUnit.SECONDS.convert(watch.stop(), TimeUnit.MILLISECONDS); 249 250 LOG.info("Graceful shutdown of " + routesOrdered.size() + " routes completed in " + seconds + " seconds"); 251 return true; 252 } 253 254 @Override 255 public boolean forceShutdown(Service service) { 256 return forceShutdown; 257 } 258 259 @Override 260 public boolean hasTimeoutOccurred() { 261 return timeoutOccurred.get(); 262 } 263 264 public void setTimeout(long timeout) { 265 if (timeout <= 0) { 266 throw new IllegalArgumentException("Timeout must be a positive value"); 267 } 268 this.timeout = timeout; 269 } 270 271 public long getTimeout() { 272 return timeout; 273 } 274 275 public void setTimeUnit(TimeUnit timeUnit) { 276 this.timeUnit = timeUnit; 277 } 278 279 public TimeUnit getTimeUnit() { 280 return timeUnit; 281 } 282 283 public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) { 284 this.shutdownNowOnTimeout = shutdownNowOnTimeout; 285 } 286 287 public boolean isShutdownNowOnTimeout() { 288 return shutdownNowOnTimeout; 289 } 290 291 public boolean isShutdownRoutesInReverseOrder() { 292 return shutdownRoutesInReverseOrder; 293 } 294 295 public void setShutdownRoutesInReverseOrder(boolean shutdownRoutesInReverseOrder) { 296 this.shutdownRoutesInReverseOrder = shutdownRoutesInReverseOrder; 297 } 298 299 public boolean isSuppressLoggingOnTimeout() { 300 return suppressLoggingOnTimeout; 301 } 302 303 public void setSuppressLoggingOnTimeout(boolean suppressLoggingOnTimeout) { 304 this.suppressLoggingOnTimeout = suppressLoggingOnTimeout; 305 } 306 307 public boolean isLogInflightExchangesOnTimeout() { 308 return logInflightExchangesOnTimeout; 309 } 310 311 public void setLogInflightExchangesOnTimeout(boolean logInflightExchangesOnTimeout) { 312 this.logInflightExchangesOnTimeout = logInflightExchangesOnTimeout; 313 } 314 315 public CamelContext getCamelContext() { 316 return camelContext; 317 } 318 319 public void setCamelContext(CamelContext camelContext) { 320 this.camelContext = camelContext; 321 } 322 323 public Future<?> getCurrentShutdownTaskFuture() { 324 return currentShutdownTaskFuture; 325 } 326 327 /** 328 * Shutdown all the consumers immediately. 329 * 330 * @param routes the routes to shutdown 331 */ 332 protected void shutdownRoutesNow(List<RouteStartupOrder> routes) { 333 for (RouteStartupOrder order : routes) { 334 335 // set the route to shutdown as fast as possible by stopping after 336 // it has completed its current task 337 ShutdownRunningTask current = order.getRoute().getRouteContext().getShutdownRunningTask(); 338 if (current != ShutdownRunningTask.CompleteCurrentTaskOnly) { 339 LOG.debug("Changing shutdownRunningTask from {} to " + ShutdownRunningTask.CompleteCurrentTaskOnly 340 + " on route {} to shutdown faster", current, order.getRoute().getId()); 341 order.getRoute().getRouteContext().setShutdownRunningTask(ShutdownRunningTask.CompleteCurrentTaskOnly); 342 } 343 344 for (Consumer consumer : order.getInputs()) { 345 shutdownNow(consumer); 346 } 347 } 348 } 349 350 /** 351 * Shutdown all the consumers immediately. 352 * 353 * @param consumers the consumers to shutdown 354 */ 355 protected void shutdownNow(List<Consumer> consumers) { 356 for (Consumer consumer : consumers) { 357 shutdownNow(consumer); 358 } 359 } 360 361 /** 362 * Shutdown the consumer immediately. 363 * 364 * @param consumer the consumer to shutdown 365 */ 366 protected static void shutdownNow(Consumer consumer) { 367 LOG.trace("Shutting down: {}", consumer); 368 369 // allow us to do custom work before delegating to service helper 370 try { 371 ServiceHelper.stopService(consumer); 372 } catch (Throwable e) { 373 LOG.warn("Error occurred while shutting down route: " + consumer + ". This exception will be ignored.", e); 374 // fire event 375 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 376 } 377 378 LOG.trace("Shutdown complete for: {}", consumer); 379 } 380 381 /** 382 * Suspends/stops the consumer immediately. 383 * 384 * @param consumer the consumer to suspend 385 */ 386 protected static void suspendNow(Consumer consumer) { 387 LOG.trace("Suspending: {}", consumer); 388 389 // allow us to do custom work before delegating to service helper 390 try { 391 ServiceHelper.suspendService(consumer); 392 } catch (Throwable e) { 393 LOG.warn("Error occurred while suspending route: " + consumer + ". This exception will be ignored.", e); 394 // fire event 395 EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); 396 } 397 398 LOG.trace("Suspend complete for: {}", consumer); 399 } 400 401 private ExecutorService getExecutorService() { 402 if (executor == null) { 403 // use a thread pool that allow to terminate idle threads so they do not hang around forever 404 executor = camelContext.getExecutorServiceManager().newThreadPool(this, "ShutdownTask", 0, 1); 405 } 406 return executor; 407 } 408 409 @Override 410 protected void doStart() throws Exception { 411 ObjectHelper.notNull(camelContext, "CamelContext"); 412 // reset option 413 forceShutdown = false; 414 timeoutOccurred.set(false); 415 } 416 417 @Override 418 protected void doStop() throws Exception { 419 // noop 420 } 421 422 @Override 423 protected void doShutdown() throws Exception { 424 if (executor != null) { 425 // force shutting down as we are shutting down Camel 426 camelContext.getExecutorServiceManager().shutdownNow(executor); 427 // should clear executor so we can restart by creating a new thread pool 428 executor = null; 429 } 430 } 431 432 /** 433 * Prepares the services for shutdown, by invoking the {@link ShutdownPrepared#prepareShutdown(boolean)} method 434 * on the service if it implement this interface. 435 * 436 * @param service the service 437 * @param forced whether to force shutdown 438 * @param includeChildren whether to prepare the child of the service as well 439 */ 440 private static void prepareShutdown(Service service, boolean forced, boolean includeChildren, boolean suppressLogging) { 441 Set<Service> list; 442 if (includeChildren) { 443 // include error handlers as we want to prepare them for shutdown as well 444 list = ServiceHelper.getChildServices(service, true); 445 } else { 446 list = new LinkedHashSet<Service>(1); 447 list.add(service); 448 } 449 450 for (Service child : list) { 451 if (child instanceof ShutdownPrepared) { 452 try { 453 LOG.trace("Preparing {} shutdown on {}", forced ? "forced" : "", child); 454 ((ShutdownPrepared) child).prepareShutdown(forced); 455 } catch (Exception e) { 456 if (suppressLogging) { 457 LOG.trace("Error during prepare shutdown on " + child + ". This exception will be ignored.", e); 458 } else { 459 LOG.warn("Error during prepare shutdown on " + child + ". This exception will be ignored.", e); 460 } 461 } 462 } 463 } 464 } 465 466 /** 467 * Holder for deferred consumers 468 */ 469 static class ShutdownDeferredConsumer { 470 private final Route route; 471 private final Consumer consumer; 472 473 ShutdownDeferredConsumer(Route route, Consumer consumer) { 474 this.route = route; 475 this.consumer = consumer; 476 } 477 478 Route getRoute() { 479 return route; 480 } 481 482 Consumer getConsumer() { 483 return consumer; 484 } 485 } 486 487 /** 488 * Shutdown task which shutdown all the routes in a graceful manner. 489 */ 490 static class ShutdownTask implements Runnable { 491 492 private final CamelContext context; 493 private final List<RouteStartupOrder> routes; 494 private final boolean suspendOnly; 495 private final boolean abortAfterTimeout; 496 private final long timeout; 497 private final TimeUnit timeUnit; 498 private final AtomicBoolean timeoutOccurred; 499 500 public ShutdownTask(CamelContext context, List<RouteStartupOrder> routes, long timeout, TimeUnit timeUnit, 501 boolean suspendOnly, boolean abortAfterTimeout, AtomicBoolean timeoutOccurred) { 502 this.context = context; 503 this.routes = routes; 504 this.suspendOnly = suspendOnly; 505 this.abortAfterTimeout = abortAfterTimeout; 506 this.timeout = timeout; 507 this.timeUnit = timeUnit; 508 this.timeoutOccurred = timeoutOccurred; 509 } 510 511 public void run() { 512 // the strategy in this run method is to 513 // 1) go over the routes and shutdown those routes which can be shutdown asap 514 // some routes will be deferred to shutdown at the end, as they are needed 515 // by other routes so they can complete their tasks 516 // 2) wait until all inflight and pending exchanges has been completed 517 // 3) shutdown the deferred routes 518 519 LOG.debug("There are {} routes to {}", routes.size(), suspendOnly ? "suspend" : "shutdown"); 520 521 // list of deferred consumers to shutdown when all exchanges has been completed routed 522 // and thus there are no more inflight exchanges so they can be safely shutdown at that time 523 List<ShutdownDeferredConsumer> deferredConsumers = new ArrayList<ShutdownDeferredConsumer>(); 524 for (RouteStartupOrder order : routes) { 525 526 ShutdownRoute shutdownRoute = order.getRoute().getRouteContext().getShutdownRoute(); 527 ShutdownRunningTask shutdownRunningTask = order.getRoute().getRouteContext().getShutdownRunningTask(); 528 529 if (LOG.isTraceEnabled()) { 530 LOG.trace("{}{} with options [{},{}]", 531 new Object[]{suspendOnly ? "Suspending route: " : "Shutting down route: ", 532 order.getRoute().getId(), shutdownRoute, shutdownRunningTask}); 533 } 534 535 for (Consumer consumer : order.getInputs()) { 536 537 boolean suspend = false; 538 539 // assume we should shutdown if we are not deferred 540 boolean shutdown = shutdownRoute != ShutdownRoute.Defer; 541 542 if (shutdown) { 543 // if we are to shutdown then check whether we can suspend instead as its a more 544 // gentle way to graceful shutdown 545 546 // some consumers do not support shutting down so let them decide 547 // if a consumer is suspendable then prefer to use that and then shutdown later 548 if (consumer instanceof ShutdownAware) { 549 shutdown = !((ShutdownAware) consumer).deferShutdown(shutdownRunningTask); 550 } 551 if (shutdown && consumer instanceof SuspendableService) { 552 // we prefer to suspend over shutdown 553 suspend = true; 554 } 555 } 556 557 // log at info level when a route has been shutdown (otherwise log at debug level to not be too noisy) 558 if (suspend) { 559 // only suspend it and then later shutdown it 560 suspendNow(consumer); 561 // add it to the deferred list so the route will be shutdown later 562 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 563 LOG.debug("Route: {} suspended and shutdown deferred, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 564 } else if (shutdown) { 565 shutdownNow(consumer); 566 LOG.info("Route: {} shutdown complete, was consuming from: {}", order.getRoute().getId(), order.getRoute().getEndpoint()); 567 } else { 568 // we will stop it later, but for now it must run to be able to help all inflight messages 569 // be safely completed 570 deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); 571 LOG.debug("Route: " + order.getRoute().getId() + (suspendOnly ? " shutdown deferred." : " suspension deferred.")); 572 } 573 } 574 } 575 576 // notify the services we intend to shutdown 577 for (RouteStartupOrder order : routes) { 578 for (Service service : order.getServices()) { 579 // skip the consumer as we handle that specially 580 if (service instanceof Consumer) { 581 continue; 582 } 583 prepareShutdown(service, false, true, false); 584 } 585 } 586 587 // wait till there are no more pending and inflight messages 588 boolean done = false; 589 long loopDelaySeconds = 1; 590 long loopCount = 0; 591 while (!done && !timeoutOccurred.get()) { 592 int size = 0; 593 // number of inflights per route 594 final Map<String, Integer> routeInflight = new LinkedHashMap<String, Integer>(); 595 596 for (RouteStartupOrder order : routes) { 597 int inflight = context.getInflightRepository().size(order.getRoute().getId()); 598 for (Consumer consumer : order.getInputs()) { 599 // include any additional pending exchanges on some consumers which may have internal 600 // memory queues such as seda 601 if (consumer instanceof ShutdownAware) { 602 inflight += ((ShutdownAware) consumer).getPendingExchangesSize(); 603 } 604 } 605 if (inflight > 0) { 606 String routeId = order.getRoute().getId(); 607 routeInflight.put(routeId, inflight); 608 size += inflight; 609 LOG.trace("{} inflight and pending exchanges for route: {}", inflight, routeId); 610 } 611 } 612 if (size > 0) { 613 try { 614 // build a message with inflight per route 615 CollectionStringBuffer csb = new CollectionStringBuffer(); 616 for (Map.Entry<String, Integer> entry : routeInflight.entrySet()) { 617 String row = String.format("%s = %s", entry.getKey(), entry.getValue()); 618 csb.append(row); 619 } 620 621 String msg = "Waiting as there are still " + size + " inflight and pending exchanges to complete, timeout in " 622 + (TimeUnit.SECONDS.convert(timeout, timeUnit) - (loopCount++ * loopDelaySeconds)) + " seconds."; 623 msg += " Inflights per route: [" + csb.toString() + "]"; 624 625 LOG.info(msg); 626 627 // log verbose if DEBUG logging is enabled 628 logInflightExchanges(context, routes, false); 629 630 Thread.sleep(loopDelaySeconds * 1000); 631 } catch (InterruptedException e) { 632 if (abortAfterTimeout) { 633 LOG.warn("Interrupted while waiting during graceful shutdown, will abort."); 634 return; 635 } else { 636 LOG.warn("Interrupted while waiting during graceful shutdown, will force shutdown now."); 637 break; 638 } 639 } 640 } else { 641 done = true; 642 } 643 } 644 645 // prepare for shutdown 646 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 647 Consumer consumer = deferred.getConsumer(); 648 if (consumer instanceof ShutdownAware) { 649 LOG.trace("Route: {} preparing to shutdown.", deferred.getRoute().getId()); 650 boolean forced = context.getShutdownStrategy().forceShutdown(consumer); 651 boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); 652 prepareShutdown(consumer, forced, false, suppress); 653 LOG.debug("Route: {} preparing to shutdown complete.", deferred.getRoute().getId()); 654 } 655 } 656 657 // now all messages has been completed then stop the deferred consumers 658 for (ShutdownDeferredConsumer deferred : deferredConsumers) { 659 Consumer consumer = deferred.getConsumer(); 660 if (suspendOnly) { 661 suspendNow(consumer); 662 LOG.info("Route: {} suspend complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 663 } else { 664 shutdownNow(consumer); 665 LOG.info("Route: {} shutdown complete, was consuming from: {}", deferred.getRoute().getId(), deferred.getConsumer().getEndpoint()); 666 } 667 } 668 669 // now the route consumers has been shutdown, then prepare route services for shutdown 670 for (RouteStartupOrder order : routes) { 671 for (Service service : order.getServices()) { 672 boolean forced = context.getShutdownStrategy().forceShutdown(service); 673 boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); 674 prepareShutdown(service, forced, true, suppress); 675 } 676 } 677 } 678 679 } 680 681 /** 682 * Logs information about the inflight exchanges 683 * 684 * @param infoLevel <tt>true</tt> to log at INFO level, <tt>false</tt> to log at DEBUG level 685 */ 686 protected static void logInflightExchanges(CamelContext camelContext, List<RouteStartupOrder> routes, boolean infoLevel) { 687 // check if we need to log 688 if (!infoLevel && !LOG.isDebugEnabled()) { 689 return; 690 } 691 692 Collection<InflightRepository.InflightExchange> inflights = camelContext.getInflightRepository().browse(); 693 int size = inflights.size(); 694 if (size == 0) { 695 return; 696 } 697 698 // filter so inflight must start from any of the routes 699 Set<String> routeIds = new HashSet<String>(); 700 for (RouteStartupOrder route : routes) { 701 routeIds.add(route.getRoute().getId()); 702 } 703 Collection<InflightRepository.InflightExchange> filtered = new ArrayList<InflightRepository.InflightExchange>(); 704 for (InflightRepository.InflightExchange inflight : inflights) { 705 String routeId = inflight.getExchange().getFromRouteId(); 706 if (routeIds.contains(routeId)) { 707 filtered.add(inflight); 708 } 709 } 710 711 size = filtered.size(); 712 if (size == 0) { 713 return; 714 } 715 716 StringBuilder sb = new StringBuilder("There are " + size + " inflight exchanges:"); 717 for (InflightRepository.InflightExchange inflight : filtered) { 718 sb.append("\n\tInflightExchange: [exchangeId=").append(inflight.getExchange().getExchangeId()) 719 .append(", fromRouteId=").append(inflight.getExchange().getFromRouteId()) 720 .append(", routeId=").append(inflight.getRouteId()) 721 .append(", nodeId=").append(inflight.getNodeId()) 722 .append(", elapsed=").append(inflight.getElapsed()) 723 .append(", duration=").append(inflight.getDuration()) 724 .append("]"); 725 } 726 727 if (infoLevel) { 728 LOG.info(sb.toString()); 729 } else { 730 LOG.debug(sb.toString()); 731 } 732 } 733 734}