All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.impl.engine.DefaultShutdownStrategy Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.impl.engine;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.Consumer;
import org.apache.camel.LoggingLevel;
import org.apache.camel.Route;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.Service;
import org.apache.camel.ShutdownRoute;
import org.apache.camel.ShutdownRunningTask;
import org.apache.camel.Suspendable;
import org.apache.camel.spi.CamelLogger;
import org.apache.camel.spi.InflightRepository;
import org.apache.camel.spi.RouteStartupOrder;
import org.apache.camel.spi.ShutdownAware;
import org.apache.camel.spi.ShutdownPrepared;
import org.apache.camel.spi.ShutdownStrategy;
import org.apache.camel.support.EventHelper;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StopWatch;
import org.apache.camel.util.TimeUtils;
import org.apache.camel.util.URISupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default {@link org.apache.camel.spi.ShutdownStrategy} which uses graceful shutdown.
 * 

* Graceful shutdown ensures that any inflight and pending messages will be taken into account and it will wait until * these exchanges has been completed. *

* This strategy will perform graceful shutdown in two steps: *

    *
  • Graceful - By suspending/stopping consumers, and let any in-flight exchanges complete
  • *
  • Forced - After a given period of time, a timeout occurred and if there are still pending exchanges to complete, * then a more aggressive forced strategy is performed.
  • *
* The idea by the graceful shutdown strategy, is to stop taking in more new messages, and allow any existing * inflight messages to complete. Then when there is no more inflight messages then the routes can be fully shutdown. * This mean that if there is inflight messages then we will have to wait for these messages to complete. If they do not * complete after a period of time, then a timeout triggers. And then a more aggressive strategy takes over. *

* The idea by the forced shutdown strategy, is to stop continue processing messages. And force routes and its * services to shutdown now. There is a risk when shutting down now, that some resources is not properly shutdown, which * can cause side effects. The timeout value is by default 45 seconds, but can be customized. *

* As this strategy will politely wait until all exchanges has been completed it can potential wait for a long time, and * hence why a timeout value can be set. When the timeout triggers you can also specify whether the remainder consumers * should be shutdown now or ignore. *

* Will by default use a timeout of 45 seconds by which it will shutdown now the remaining consumers. This ensures that * when shutting down Camel it at some point eventually will shutdown. This behavior can of course be configured using * the {@link #setTimeout(long)} and {@link #setShutdownNowOnTimeout(boolean)} methods. *

* Routes will by default be shutdown in the reverse order of which they where started. You can customize this using the * {@link #setShutdownRoutesInReverseOrder(boolean)} method. *

* After route consumers have been shutdown, then any {@link ShutdownPrepared} services on the routes is being prepared * for shutdown, by invoking {@link ShutdownPrepared#prepareShutdown(boolean,boolean)} which force=false. *

* Then if a timeout occurred and the strategy has been configured with shutdown-now on timeout, then the strategy * performs a more aggressive forced shutdown, by forcing all consumers to shutdown and then invokes * {@link ShutdownPrepared#prepareShutdown(boolean,boolean)} with force=true on the services. This allows the * services to know they should force shutdown now. *

* When timeout occurred and a forced shutdown is happening, then there may be threads/tasks which are still inflight * which may be rejected continued being routed. By default this can cause WARN and ERRORs to be logged. The option * {@link #setSuppressLoggingOnTimeout(boolean)} can be used to suppress these logs, so they are logged at TRACE level * instead. *

* Also when a timeout occurred then information about the inflight exchanges is logged, if * {@link #isLogInflightExchangesOnTimeout()} is enabled (is by default). This allows end users to known where these * inflight exchanges currently are in the route(s), and how long time they have been inflight. *

* This information can also be obtained from the {@link org.apache.camel.spi.InflightRepository} at all time during * runtime. */ public class DefaultShutdownStrategy extends ServiceSupport implements ShutdownStrategy, CamelContextAware { private static final Logger LOG = LoggerFactory.getLogger(DefaultShutdownStrategy.class); private final CamelLogger logger = new CamelLogger(LOG, LoggingLevel.DEBUG); private CamelContext camelContext; private ExecutorService executor; private long timeout = 45; private TimeUnit timeUnit = TimeUnit.SECONDS; private boolean shutdownNowOnTimeout = true; private boolean shutdownRoutesInReverseOrder = true; private boolean suppressLoggingOnTimeout; private boolean logInflightExchangesOnTimeout = true; private boolean forceShutdown; private final AtomicBoolean timeoutOccurred = new AtomicBoolean(); private volatile Future currentShutdownTaskFuture; public DefaultShutdownStrategy() { } public DefaultShutdownStrategy(CamelContext camelContext) { this.camelContext = camelContext; } @Override public void shutdown(CamelContext context, List routes) throws Exception { shutdown(context, routes, getTimeout(), getTimeUnit()); } @Override public void shutdownForced(CamelContext context, List routes) throws Exception { doShutdown(context, routes, getTimeout(), getTimeUnit(), false, false, true); } @Override public void suspend(CamelContext context, List routes) throws Exception { doShutdown(context, routes, getTimeout(), getTimeUnit(), true, false, false); } @Override public void shutdown(CamelContext context, List routes, long timeout, TimeUnit timeUnit) throws Exception { doShutdown(context, routes, timeout, timeUnit, false, false, false); } @Override public boolean shutdown( CamelContext context, RouteStartupOrder route, long timeout, TimeUnit timeUnit, boolean abortAfterTimeout) throws Exception { List routes = Collections.singletonList(route); return doShutdown(context, routes, timeout, timeUnit, false, abortAfterTimeout, false); } @Override public void suspend(CamelContext context, List routes, long timeout, TimeUnit timeUnit) throws Exception { doShutdown(context, routes, timeout, timeUnit, true, false, false); } protected boolean doShutdown( CamelContext context, List routes, long timeout, TimeUnit timeUnit, boolean suspendOnly, boolean abortAfterTimeout, boolean forceShutdown) throws Exception { // timeout must be a positive value if (timeout <= 0) { throw new IllegalArgumentException("Timeout must be a positive value"); } // just return if no routes to shutdown if (routes.isEmpty()) { return true; } StopWatch watch = new StopWatch(); // at first sort according to route startup order Comparator comparator = Comparator.comparingInt(RouteStartupOrder::getStartupOrder); if (shutdownRoutesInReverseOrder) { comparator = comparator.reversed(); } List routesOrdered = new ArrayList<>(routes); routesOrdered.sort(comparator); if (logger.shouldLog()) { final String action = suspendOnly ? "suspend" : "shutdown"; String msg = String.format("Starting to graceful %s %s routes (timeout %s %s)", action, routesOrdered.size(), timeout, timeUnit.toString().toLowerCase(Locale.ENGLISH)); logger.log(msg); } // use another thread to perform the shutdowns so we can support timeout timeoutOccurred.set(false); try { currentShutdownTaskFuture = getExecutorService().submit(new ShutdownTask( context, routesOrdered, timeout, timeUnit, suspendOnly, abortAfterTimeout, timeoutOccurred, isLogInflightExchangesOnTimeout())); currentShutdownTaskFuture.get(timeout, timeUnit); } catch (RejectedExecutionException e) { // the task was rejected } catch (ExecutionException e) { // unwrap execution exception throw RuntimeCamelException.wrapRuntimeCamelException(e.getCause()); } catch (Exception e) { // either timeout or interrupted exception was thrown so this is okay // as interrupted would mean cancel was called on the currentShutdownTaskFuture to signal a forced timeout // we hit a timeout, so set the flag timeoutOccurred.set(true); // timeout then cancel the task if (currentShutdownTaskFuture != null) { currentShutdownTaskFuture.cancel(true); } // signal we are forcing shutdown now, since timeout occurred this.forceShutdown = forceShutdown; // if set, stop processing and return false to indicate that the shutdown is aborting if (!forceShutdown && abortAfterTimeout) { LOG.warn("Timeout occurred during graceful shutdown. Aborting the shutdown now." + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); // we attempt to force shutdown so lets log the current inflight exchanges which are affected logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); return false; } else { if (forceShutdown || shutdownNowOnTimeout) { LOG.warn("Timeout occurred during graceful shutdown. Forcing the routes to be shutdown now." + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); // we attempt to force shutdown so lets log the current inflight exchanges which are affected logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); // force the routes to shutdown now shutdownRoutesNow(routesOrdered); // now the route consumers has been shutdown, then prepare route services for shutdown now (forced) for (RouteStartupOrder order : routes) { for (Service service : order.getServices()) { prepareShutdown(service, false, true, true, isSuppressLoggingOnTimeout()); } } } else { LOG.warn("Timeout occurred during graceful shutdown. Will ignore shutting down the remainder routes." + " Notice: some resources may still be running as graceful shutdown did not complete successfully."); logInflightExchanges(context, routes, isLogInflightExchangesOnTimeout()); } } } finally { currentShutdownTaskFuture = null; } if (logger.shouldLog()) { logger.log(String.format("Graceful shutdown of %s routes completed in %s", routesOrdered.size(), TimeUtils.printDuration(watch.taken(), true))); } return true; } @Override public boolean isForceShutdown() { return forceShutdown; } @Override public boolean hasTimeoutOccurred() { return isTimeoutOccurred(); } @Override public boolean isTimeoutOccurred() { return timeoutOccurred.get(); } @Override public void setTimeout(long timeout) { if (timeout <= 0) { throw new IllegalArgumentException("Timeout must be a positive value"); } this.timeout = timeout; } @Override public long getTimeout() { return timeout; } @Override public void setTimeUnit(TimeUnit timeUnit) { this.timeUnit = timeUnit; } @Override public TimeUnit getTimeUnit() { return timeUnit; } @Override public void setShutdownNowOnTimeout(boolean shutdownNowOnTimeout) { this.shutdownNowOnTimeout = shutdownNowOnTimeout; } @Override public boolean isShutdownNowOnTimeout() { return shutdownNowOnTimeout; } @Override public boolean isShutdownRoutesInReverseOrder() { return shutdownRoutesInReverseOrder; } @Override public void setShutdownRoutesInReverseOrder(boolean shutdownRoutesInReverseOrder) { this.shutdownRoutesInReverseOrder = shutdownRoutesInReverseOrder; } @Override public boolean isSuppressLoggingOnTimeout() { return suppressLoggingOnTimeout; } @Override public void setSuppressLoggingOnTimeout(boolean suppressLoggingOnTimeout) { this.suppressLoggingOnTimeout = suppressLoggingOnTimeout; } @Override public boolean isLogInflightExchangesOnTimeout() { return logInflightExchangesOnTimeout; } @Override public void setLogInflightExchangesOnTimeout(boolean logInflightExchangesOnTimeout) { this.logInflightExchangesOnTimeout = logInflightExchangesOnTimeout; } @Override public LoggingLevel getLoggingLevel() { return logger.getLevel(); } @Override public void setLoggingLevel(LoggingLevel loggingLevel) { this.logger.setLevel(loggingLevel); } @Override public CamelContext getCamelContext() { return camelContext; } @Override public void setCamelContext(CamelContext camelContext) { this.camelContext = camelContext; } /** * Future for the current shutdown task, when a task is in progress. *

* Important: This API is only for advanced use-cases. */ public Future getCurrentShutdownTaskFuture() { return currentShutdownTaskFuture; } /** * Shutdown all the consumers immediately. * * @param routes the routes to shutdown */ protected void shutdownRoutesNow(List routes) { for (RouteStartupOrder order : routes) { // set the route to shutdown as fast as possible by stopping after // it has completed its current task ShutdownRunningTask current = order.getRoute().getShutdownRunningTask(); if (current != ShutdownRunningTask.CompleteCurrentTaskOnly) { LOG.debug("Changing shutdownRunningTask from {} to {} on route {} to shutdown faster", ShutdownRunningTask.CompleteCurrentTaskOnly, current, order.getRoute().getId()); order.getRoute().setShutdownRunningTask(ShutdownRunningTask.CompleteCurrentTaskOnly); } order.getRoute().getProperties().put("forcedShutdown", true); // shutdown the route consumer shutdownNow(order.getRoute().getId(), order.getInput()); } } /** * Shutdown all the consumers immediately. * * @param routeId the route id to suspend * @param consumers the consumers to shutdown */ protected void shutdownNow(String routeId, List consumers) { for (Consumer consumer : consumers) { shutdownNow(routeId, consumer); } } /** * Shutdown the consumer immediately. * * @param routeId the route id to suspend * @param consumer the consumer to shutdown */ protected void shutdownNow(String routeId, Consumer consumer) { LOG.trace("Shutting down: {}", consumer); // allow us to do custom work before delegating to service helper try { ServiceHelper.stopService(consumer); } catch (Exception e) { LOG.warn("Error occurred while shutting down route: {}. This exception will be ignored.", routeId, e); // fire event EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); } LOG.trace("Shutdown complete for: {}", consumer); } /** * Suspends/stops the consumer immediately. * * @param routeId the route id to suspend * @param consumer the consumer to suspend */ protected void suspendNow(String routeId, Consumer consumer) { LOG.trace("Suspending: {}", consumer); // allow us to do custom work before delegating to service helper try { ServiceHelper.suspendService(consumer); } catch (Exception e) { LOG.warn("Error occurred while suspending route: {}. This exception will be ignored.", routeId, e); // fire event EventHelper.notifyServiceStopFailure(consumer.getEndpoint().getCamelContext(), consumer, e); } LOG.trace("Suspend complete for: {}", consumer); } private ExecutorService getExecutorService() { if (executor == null) { // use a thread pool that allow to terminate idle threads so they do not hang around forever executor = camelContext.getExecutorServiceManager().newSingleThreadExecutor(this, "ShutdownTask"); } return executor; } @Override protected void doStart() throws Exception { ObjectHelper.notNull(camelContext, "CamelContext"); // reset option forceShutdown = false; timeoutOccurred.set(false); } @Override protected void doShutdown() throws Exception { if (executor != null) { // force shutting down as we are shutting down Camel camelContext.getExecutorServiceManager().shutdownNow(executor); // should clear executor so we can restart by creating a new thread pool executor = null; } } /** * Prepares the services for shutdown, by invoking the {@link ShutdownPrepared#prepareShutdown(boolean, boolean)} * method on the service if it implement this interface. * * @param service the service * @param forced whether to force shutdown * @param includeChildren whether to prepare the child of the service as well */ private void prepareShutdown( Service service, boolean suspendOnly, boolean forced, boolean includeChildren, boolean suppressLogging) { Set list; if (includeChildren) { // include error handlers as we want to prepare them for shutdown as well list = ServiceHelper.getChildServices(service, true); } else { list = new LinkedHashSet<>(1); list.add(service); } for (Service child : list) { if (child instanceof ShutdownPrepared shutdownPrepared) { try { LOG.trace("Preparing (forced: {}) shutdown on: {}", forced, child); shutdownPrepared.prepareShutdown(suspendOnly, forced); } catch (Exception e) { if (suppressLogging) { LOG.trace("Error during prepare shutdown on {}. This exception will be ignored.", child, e); } else { LOG.warn("Error during prepare shutdown on {}. This exception will be ignored.", child, e); } } } } } /** * Holder for deferred consumers */ static class ShutdownDeferredConsumer { private final Route route; private final Consumer consumer; ShutdownDeferredConsumer(Route route, Consumer consumer) { this.route = route; this.consumer = consumer; } Route getRoute() { return route; } Consumer getConsumer() { return consumer; } } /** * Shutdown task which shutdown all the routes in a graceful manner. */ class ShutdownTask implements Runnable { private final CamelContext context; private final List routes; private final boolean suspendOnly; private final boolean abortAfterTimeout; private final long timeout; private final TimeUnit timeUnit; private final AtomicBoolean timeoutOccurred; private final boolean logInflightExchangesOnTimeout; ShutdownTask(CamelContext context, List routes, long timeout, TimeUnit timeUnit, boolean suspendOnly, boolean abortAfterTimeout, AtomicBoolean timeoutOccurred, boolean logInflightExchangesOnTimeout) { this.context = context; this.routes = routes; this.suspendOnly = suspendOnly; this.abortAfterTimeout = abortAfterTimeout; this.timeout = timeout; this.timeUnit = timeUnit; this.timeoutOccurred = timeoutOccurred; this.logInflightExchangesOnTimeout = logInflightExchangesOnTimeout; } // Disable BusyWait as we're only waiting on seconds increment, so any other // strategy would not be much more efficient @SuppressWarnings("BusyWait") @Override public void run() { // the strategy in this run method is to // 1) go over the routes and shutdown those routes which can be shutdown asap // some routes will be deferred to shutdown at the end, as they are needed // by other routes so they can complete their tasks // 2) wait until all inflight and pending exchanges has been completed // 3) shutdown the deferred routes LOG.debug("There are {} routes to {}", routes.size(), suspendOnly ? "suspend" : "shutdown"); // list of deferred consumers to shutdown when all exchanges has been completed routed // and thus there are no more inflight exchanges so they can be safely shutdown at that time List deferredConsumers = new ArrayList<>(); for (RouteStartupOrder order : routes) { ShutdownRoute shutdownRoute = order.getRoute().getShutdownRoute(); ShutdownRunningTask shutdownRunningTask = order.getRoute().getShutdownRunningTask(); if (LOG.isTraceEnabled()) { LOG.trace("{}{} with options [{},{}]", suspendOnly ? "Suspending route: " : "Shutting down route: ", order.getRoute().getId(), shutdownRoute, shutdownRunningTask); } Consumer consumer = order.getInput(); boolean suspend = false; // assume we should shutdown if we are not deferred boolean shutdown = shutdownRoute != ShutdownRoute.Defer; if (shutdown) { // if we are to shutdown then check whether we can suspend instead as its a more // gentle way to graceful shutdown // some consumers do not support shutting down so let them decide // if a consumer is suspendable then prefer to use that and then shutdown later if (consumer instanceof ShutdownAware shutdownAware) { shutdown = !shutdownAware.deferShutdown(shutdownRunningTask); } if (shutdown && consumer instanceof Suspendable) { // we prefer to suspend over shutdown suspend = true; } } // log at info level when a route has been shutdown (otherwise log at debug level to not be too noisy) if (suspend) { // only suspend it and then later shutdown it suspendNow(order.getRoute().getId(), consumer); // add it to the deferred list so the route will be shutdown later deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); // use basic endpoint uri to not log verbose details or potential sensitive data String uri = order.getRoute().getEndpoint().getEndpointBaseUri(); uri = URISupport.sanitizeUri(uri); LOG.debug("Route: {} suspended and shutdown deferred, was consuming from: {}", order.getRoute().getId(), uri); } else if (shutdown) { shutdownNow(order.getRoute().getId(), consumer); // use basic endpoint uri to not log verbose details or potential sensitive data String uri = order.getRoute().getEndpoint().getEndpointBaseUri(); uri = URISupport.sanitizeUri(uri); if (logger.shouldLog()) { logger.log(String.format("Route: %s shutdown complete, was consuming from: %s", order.getRoute().getId(), uri)); } } else { // we will stop it later, but for now it must run to be able to help all inflight messages // be safely completed deferredConsumers.add(new ShutdownDeferredConsumer(order.getRoute(), consumer)); LOG.debug("Route: {} {}", order.getRoute().getId(), suspendOnly ? "shutdown deferred." : "suspension deferred."); } } // notify the services we intend to shutdown for (RouteStartupOrder order : routes) { for (Service service : order.getServices()) { // skip the consumer as we handle that specially if (service instanceof Consumer) { continue; } prepareShutdown(service, suspendOnly, false, true, false); } } // wait till there are no more pending and inflight messages boolean done = false; long loopDelaySeconds = 1; long loopCount = 0; while (!done && !timeoutOccurred.get()) { int size = 0; // number of inflights per route final Map routeInflight = new LinkedHashMap<>(); for (RouteStartupOrder order : routes) { int inflight = context.getInflightRepository().size(order.getRoute().getId()); inflight += getPendingInflightExchanges(order); if (inflight > 0) { String routeId = order.getRoute().getId(); routeInflight.put(routeId, inflight); size += inflight; LOG.trace("{} inflight and pending exchanges for route: {}", inflight, routeId); } } if (size > 0) { try { // build a message with inflight per route StringJoiner inflightsBuilder = new StringJoiner(", ", " Inflights per route: [", "]"); for (Map.Entry entry : routeInflight.entrySet()) { String row = String.format("%s = %s", entry.getKey(), entry.getValue()); inflightsBuilder.add(row); } String msg = "Waiting as there are still " + size + " inflight and pending exchanges to complete, timeout in " + (TimeUnit.SECONDS.convert(timeout, timeUnit) - (loopCount++ * loopDelaySeconds)) + " seconds."; msg += inflightsBuilder.toString(); LOG.info(msg); // log verbose if DEBUG logging is enabled logInflightExchanges(context, routes, logInflightExchangesOnTimeout); Thread.sleep(loopDelaySeconds * 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); if (abortAfterTimeout) { LOG.warn("Interrupted while waiting during graceful shutdown, will abort."); return; } else { LOG.warn("Interrupted while waiting during graceful shutdown, will force shutdown now."); break; } } } else { done = true; } } // prepare for shutdown for (ShutdownDeferredConsumer deferred : deferredConsumers) { Consumer consumer = deferred.getConsumer(); if (consumer instanceof ShutdownAware) { LOG.trace("Route: {} preparing to shutdown.", deferred.getRoute().getId()); boolean forced = context.getShutdownStrategy().isForceShutdown(); boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); prepareShutdown(consumer, suspendOnly, forced, false, suppress); LOG.debug("Route: {} preparing to shutdown complete.", deferred.getRoute().getId()); } } // now all messages has been completed then stop the deferred consumers for (ShutdownDeferredConsumer deferred : deferredConsumers) { Consumer consumer = deferred.getConsumer(); if (suspendOnly) { suspendNow(deferred.getRoute().getId(), consumer); // use basic endpoint uri to not log verbose details or potential sensitive data String uri = deferred.getRoute().getEndpoint().getEndpointBaseUri(); uri = URISupport.sanitizeUri(uri); if (logger.shouldLog()) { logger.log(String.format("Route: %s suspend complete, was consuming from: %s", deferred.getRoute().getId(), uri)); } } else { shutdownNow(deferred.getRoute().getId(), consumer); // use basic endpoint uri to not log verbose details or potential sensitive data String uri = deferred.getRoute().getEndpoint().getEndpointBaseUri(); uri = URISupport.sanitizeUri(uri); if (logger.shouldLog()) { logger.log(String.format("Route: %s shutdown complete, was consuming from: %s", deferred.getRoute().getId(), uri)); } } } // now the route consumers has been shutdown, then prepare route services for shutdown for (RouteStartupOrder order : routes) { for (Service service : order.getServices()) { boolean forced = context.getShutdownStrategy().isForceShutdown(); boolean suppress = context.getShutdownStrategy().isSuppressLoggingOnTimeout(); prepareShutdown(service, suspendOnly, forced, true, suppress); } } } } /** * Calculates the total number of inflight exchanges for the given route * * @param order the route * @return number of inflight exchanges */ protected static int getPendingInflightExchanges(RouteStartupOrder order) { int inflight = 0; // the consumer is the 1st service so we always get the consumer // the child services are EIPs in the routes which may also have pending // inflight exchanges (such as the aggregator) for (Service service : order.getServices()) { Set children = ServiceHelper.getChildServices(service); for (Service child : children) { if (child instanceof ShutdownAware shutdownAware) { inflight += shutdownAware.getPendingExchangesSize(); } } } return inflight; } /** * Logs information about the inflight exchanges * * @param infoLevel true to log at INFO level, false to log at DEBUG level */ protected void logInflightExchanges(CamelContext camelContext, List routes, boolean infoLevel) { // check if we need to log if (!infoLevel && !LOG.isDebugEnabled()) { return; } Collection inflights = camelContext.getInflightRepository().browse(); int size = inflights.size(); if (size == 0) { return; } // filter so inflight must start from any of the routes Set routeIds = new HashSet<>(); for (RouteStartupOrder route : routes) { routeIds.add(route.getRoute().getId()); } Collection filtered = new ArrayList<>(); for (InflightRepository.InflightExchange inflight : inflights) { String routeId = inflight.getExchange().getFromRouteId(); if (routeIds.contains(routeId)) { filtered.add(inflight); } } size = filtered.size(); if (size == 0) { return; } StringBuilder sb = new StringBuilder(512); sb.append("There are ").append(size).append(" inflight exchanges:"); for (InflightRepository.InflightExchange inflight : filtered) { sb.append("\n\tInflightExchange: [exchangeId=").append(inflight.getExchange().getExchangeId()) .append(", fromRouteId=").append(inflight.getExchange().getFromRouteId()) .append(", atRouteId=").append(inflight.getAtRouteId()) .append(", nodeId=").append(inflight.getNodeId()) .append(", elapsed=").append(inflight.getElapsed()) .append(", duration=").append(inflight.getDuration()) .append("]"); } if (infoLevel) { LOG.info(sb.toString()); } else { LOG.debug(sb.toString()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy