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