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.Iterator;
020import java.util.LinkedHashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.CopyOnWriteArrayList;
026import java.util.concurrent.ExecutorService;
027import java.util.concurrent.ScheduledExecutorService;
028import java.util.concurrent.ThreadFactory;
029import java.util.concurrent.ThreadPoolExecutor;
030import java.util.concurrent.TimeUnit;
031
032import org.apache.camel.CamelContext;
033import org.apache.camel.NamedNode;
034import org.apache.camel.StaticService;
035import org.apache.camel.ThreadPoolRejectedPolicy;
036import org.apache.camel.model.OptionalIdentifiedDefinition;
037import org.apache.camel.model.ProcessorDefinition;
038import org.apache.camel.model.ProcessorDefinitionHelper;
039import org.apache.camel.model.RouteDefinition;
040import org.apache.camel.spi.ExecutorServiceManager;
041import org.apache.camel.spi.LifecycleStrategy;
042import org.apache.camel.spi.ThreadPoolFactory;
043import org.apache.camel.spi.ThreadPoolProfile;
044import org.apache.camel.support.ServiceSupport;
045import org.apache.camel.util.ObjectHelper;
046import org.apache.camel.util.StopWatch;
047import org.apache.camel.util.TimeUtils;
048import org.apache.camel.util.URISupport;
049import org.apache.camel.util.concurrent.CamelThreadFactory;
050import org.apache.camel.util.concurrent.SizedScheduledExecutorService;
051import org.apache.camel.util.concurrent.ThreadHelper;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055/**
056 * Default {@link org.apache.camel.spi.ExecutorServiceManager}.
057 *
058 * @version 
059 */
060public class DefaultExecutorServiceManager extends ServiceSupport implements ExecutorServiceManager {
061    private static final Logger LOG = LoggerFactory.getLogger(DefaultExecutorServiceManager.class);
062
063    private final CamelContext camelContext;
064    private ThreadPoolFactory threadPoolFactory = new DefaultThreadPoolFactory();
065    private final List<ExecutorService> executorServices = new CopyOnWriteArrayList<ExecutorService>();
066    private String threadNamePattern;
067    private long shutdownAwaitTermination = 10000;
068    private String defaultThreadPoolProfileId = "defaultThreadPoolProfile";
069    private final Map<String, ThreadPoolProfile> threadPoolProfiles = new ConcurrentHashMap<String, ThreadPoolProfile>();
070    private ThreadPoolProfile defaultProfile;
071
072    public DefaultExecutorServiceManager(CamelContext camelContext) {
073        this.camelContext = camelContext;
074
075        defaultProfile = new ThreadPoolProfile(defaultThreadPoolProfileId);
076        defaultProfile.setDefaultProfile(true);
077        defaultProfile.setPoolSize(10);
078        defaultProfile.setMaxPoolSize(20);
079        defaultProfile.setKeepAliveTime(60L);
080        defaultProfile.setTimeUnit(TimeUnit.SECONDS);
081        defaultProfile.setMaxQueueSize(1000);
082        defaultProfile.setRejectedPolicy(ThreadPoolRejectedPolicy.CallerRuns);
083
084        registerThreadPoolProfile(defaultProfile);
085    }
086
087    @Override
088    public ThreadPoolFactory getThreadPoolFactory() {
089        return threadPoolFactory;
090    }
091
092    @Override
093    public void setThreadPoolFactory(ThreadPoolFactory threadPoolFactory) {
094        this.threadPoolFactory = threadPoolFactory;
095    }
096
097    @Override
098    public void registerThreadPoolProfile(ThreadPoolProfile profile) {
099        ObjectHelper.notNull(profile, "profile");
100        ObjectHelper.notEmpty(profile.getId(), "id", profile);
101        threadPoolProfiles.put(profile.getId(), profile);
102    }
103
104    @Override
105    public ThreadPoolProfile getThreadPoolProfile(String id) {
106        return threadPoolProfiles.get(id);
107    }
108
109    @Override
110    public ThreadPoolProfile getDefaultThreadPoolProfile() {
111        return getThreadPoolProfile(defaultThreadPoolProfileId);
112    }
113
114    @Override
115    public void setDefaultThreadPoolProfile(ThreadPoolProfile defaultThreadPoolProfile) {
116        threadPoolProfiles.remove(defaultThreadPoolProfileId);
117        defaultThreadPoolProfile.addDefaults(defaultProfile);
118
119        LOG.info("Using custom DefaultThreadPoolProfile: " + defaultThreadPoolProfile);
120
121        this.defaultThreadPoolProfileId = defaultThreadPoolProfile.getId();
122        defaultThreadPoolProfile.setDefaultProfile(true);
123        registerThreadPoolProfile(defaultThreadPoolProfile);
124    }
125
126    @Override
127    public String getThreadNamePattern() {
128        return threadNamePattern;
129    }
130
131    @Override
132    public void setThreadNamePattern(String threadNamePattern) {
133        // must set camel id here in the pattern and let the other placeholders be resolved on demand
134        String name = threadNamePattern.replaceFirst("#camelId#", this.camelContext.getName());
135        this.threadNamePattern = name;
136    }
137
138    @Override
139    public long getShutdownAwaitTermination() {
140        return shutdownAwaitTermination;
141    }
142
143    @Override
144    public void setShutdownAwaitTermination(long shutdownAwaitTermination) {
145        this.shutdownAwaitTermination = shutdownAwaitTermination;
146    }
147
148    @Override
149    public String resolveThreadName(String name) {
150        return ThreadHelper.resolveThreadName(threadNamePattern, name);
151    }
152
153    @Override
154    public Thread newThread(String name, Runnable runnable) {
155        ThreadFactory factory = createThreadFactory(name, true);
156        return factory.newThread(runnable);
157    }
158
159    @Override
160    public ExecutorService newDefaultThreadPool(Object source, String name) {
161        return newThreadPool(source, name, getDefaultThreadPoolProfile());
162    }
163
164    @Override
165    public ScheduledExecutorService newDefaultScheduledThreadPool(Object source, String name) {
166        return newScheduledThreadPool(source, name, getDefaultThreadPoolProfile());
167    }
168
169    @Override
170    public ExecutorService newThreadPool(Object source, String name, String profileId) {
171        ThreadPoolProfile profile = getThreadPoolProfile(profileId);
172        if (profile != null) {
173            return newThreadPool(source, name, profile);
174        } else {
175            // no profile with that id
176            return null;
177        }
178    }
179
180    @Override
181    public ExecutorService newThreadPool(Object source, String name, ThreadPoolProfile profile) {
182        String sanitizedName = URISupport.sanitizeUri(name);
183        ObjectHelper.notNull(profile, "ThreadPoolProfile");
184
185        ThreadPoolProfile defaultProfile = getDefaultThreadPoolProfile();
186        profile.addDefaults(defaultProfile);
187
188        ThreadFactory threadFactory = createThreadFactory(sanitizedName, true);
189        ExecutorService executorService = threadPoolFactory.newThreadPool(profile, threadFactory);
190        onThreadPoolCreated(executorService, source, profile.getId());
191        if (LOG.isDebugEnabled()) {
192            LOG.debug("Created new ThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, executorService});
193        }
194
195        return executorService;
196    }
197
198    @Override
199    public ExecutorService newThreadPool(Object source, String name, int poolSize, int maxPoolSize) {
200        ThreadPoolProfile profile = new ThreadPoolProfile(name);
201        profile.setPoolSize(poolSize);
202        profile.setMaxPoolSize(maxPoolSize);
203        return  newThreadPool(source, name, profile);
204    }
205
206    @Override
207    public ExecutorService newSingleThreadExecutor(Object source, String name) {
208        return newFixedThreadPool(source, name, 1);
209    }
210
211    @Override
212    public ExecutorService newCachedThreadPool(Object source, String name) {
213        String sanitizedName = URISupport.sanitizeUri(name);
214        ExecutorService answer = threadPoolFactory.newCachedThreadPool(createThreadFactory(sanitizedName, true));
215        onThreadPoolCreated(answer, source, null);
216
217        if (LOG.isDebugEnabled()) {
218            LOG.debug("Created new CachedThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, answer});
219        }
220        return answer;
221    }
222
223    @Override
224    public ExecutorService newFixedThreadPool(Object source, String name, int poolSize) {
225        ThreadPoolProfile profile = new ThreadPoolProfile(name);
226        profile.setPoolSize(poolSize);
227        profile.setMaxPoolSize(poolSize);
228        profile.setKeepAliveTime(0L);
229        return newThreadPool(source, name, profile);
230    }
231
232    @Override
233    public ScheduledExecutorService newSingleThreadScheduledExecutor(Object source, String name) {
234        return newScheduledThreadPool(source, name, 1);
235    }
236    
237    @Override
238    public ScheduledExecutorService newScheduledThreadPool(Object source, String name, ThreadPoolProfile profile) {
239        String sanitizedName = URISupport.sanitizeUri(name);
240        profile.addDefaults(getDefaultThreadPoolProfile());
241        ScheduledExecutorService answer = threadPoolFactory.newScheduledThreadPool(profile, createThreadFactory(sanitizedName, true));
242        onThreadPoolCreated(answer, source, null);
243
244        if (LOG.isDebugEnabled()) {
245            LOG.debug("Created new ScheduledThreadPool for source: {} with name: {}. -> {}", new Object[]{source, sanitizedName, answer});
246        }
247        return answer;
248    }
249
250    @Override
251    public ScheduledExecutorService newScheduledThreadPool(Object source, String name, String profileId) {
252        ThreadPoolProfile profile = getThreadPoolProfile(profileId);
253        if (profile != null) {
254            return newScheduledThreadPool(source, name, profile);
255        } else {
256            // no profile with that id
257            return null;
258        }
259    }
260
261    @Override
262    public ScheduledExecutorService newScheduledThreadPool(Object source, String name, int poolSize) {
263        ThreadPoolProfile profile = new ThreadPoolProfile(name);
264        profile.setPoolSize(poolSize);
265        return newScheduledThreadPool(source, name, profile);
266    }
267
268    @Override
269    public void shutdown(ExecutorService executorService) {
270        doShutdown(executorService, 0, false);
271    }
272
273    @Override
274    public void shutdownGraceful(ExecutorService executorService) {
275        doShutdown(executorService, getShutdownAwaitTermination(), false);
276    }
277
278    @Override
279    public void shutdownGraceful(ExecutorService executorService, long shutdownAwaitTermination) {
280        doShutdown(executorService, shutdownAwaitTermination, false);
281    }
282
283    private boolean doShutdown(ExecutorService executorService, long shutdownAwaitTermination, boolean failSafe) {
284        if (executorService == null) {
285            return false;
286        }
287
288        boolean warned = false;
289
290        // shutting down a thread pool is a 2 step process. First we try graceful, and if that fails, then we go more aggressively
291        // and try shutting down again. In both cases we wait at most the given shutdown timeout value given
292        // (total wait could then be 2 x shutdownAwaitTermination, but when we shutdown the 2nd time we are aggressive and thus
293        // we ought to shutdown much faster)
294        if (!executorService.isShutdown()) {
295            StopWatch watch = new StopWatch();
296
297            LOG.trace("Shutdown of ExecutorService: {} with await termination: {} millis", executorService, shutdownAwaitTermination);
298            executorService.shutdown();
299
300            if (shutdownAwaitTermination > 0) {
301                try {
302                    if (!awaitTermination(executorService, shutdownAwaitTermination)) {
303                        warned = true;
304                        LOG.warn("Forcing shutdown of ExecutorService: {} due first await termination elapsed.", executorService);
305                        executorService.shutdownNow();
306                        // we are now shutting down aggressively, so wait to see if we can completely shutdown or not
307                        if (!awaitTermination(executorService, shutdownAwaitTermination)) {
308                            LOG.warn("Cannot completely force shutdown of ExecutorService: {} due second await termination elapsed.", executorService);
309                        }
310                    }
311                } catch (InterruptedException e) {
312                    warned = true;
313                    LOG.warn("Forcing shutdown of ExecutorService: {} due interrupted.", executorService);
314                    // we were interrupted during shutdown, so force shutdown
315                    executorService.shutdownNow();
316                }
317            }
318
319            // if we logged at WARN level, then report at INFO level when we are complete so the end user can see this in the log
320            if (warned) {
321                LOG.info("Shutdown of ExecutorService: {} is shutdown: {} and terminated: {} took: {}.",
322                        new Object[]{executorService, executorService.isShutdown(), executorService.isTerminated(), TimeUtils.printDuration(watch.taken())});
323            } else if (LOG.isDebugEnabled()) {
324                LOG.debug("Shutdown of ExecutorService: {} is shutdown: {} and terminated: {} took: {}.",
325                    new Object[]{executorService, executorService.isShutdown(), executorService.isTerminated(), TimeUtils.printDuration(watch.taken())});
326            }
327        }
328
329        // let lifecycle strategy be notified as well which can let it be managed in JMX as well
330        ThreadPoolExecutor threadPool = null;
331        if (executorService instanceof ThreadPoolExecutor) {
332            threadPool = (ThreadPoolExecutor) executorService;
333        } else if (executorService instanceof SizedScheduledExecutorService) {
334            threadPool = ((SizedScheduledExecutorService) executorService).getScheduledThreadPoolExecutor();
335        }
336        if (threadPool != null) {
337            for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
338                lifecycle.onThreadPoolRemove(camelContext, threadPool);
339            }
340        }
341
342        // remove reference as its shutdown (do not remove if fail-safe)
343        if (!failSafe) {
344            executorServices.remove(executorService);
345        }
346
347        return warned;
348    }
349
350    @Override
351    public List<Runnable> shutdownNow(ExecutorService executorService) {
352        return doShutdownNow(executorService, false);
353    }
354
355    private List<Runnable> doShutdownNow(ExecutorService executorService, boolean failSafe) {
356        ObjectHelper.notNull(executorService, "executorService");
357
358        List<Runnable> answer = null;
359        if (!executorService.isShutdown()) {
360            if (failSafe) {
361                // log as warn, as we shutdown as fail-safe, so end user should see more details in the log.
362                LOG.warn("Forcing shutdown of ExecutorService: {}", executorService);
363            } else {
364                LOG.debug("Forcing shutdown of ExecutorService: {}", executorService);
365            }
366            answer = executorService.shutdownNow();
367            if (LOG.isTraceEnabled()) {
368                LOG.trace("Shutdown of ExecutorService: {} is shutdown: {} and terminated: {}.",
369                        new Object[]{executorService, executorService.isShutdown(), executorService.isTerminated()});
370            }
371        }
372
373        // let lifecycle strategy be notified as well which can let it be managed in JMX as well
374        ThreadPoolExecutor threadPool = null;
375        if (executorService instanceof ThreadPoolExecutor) {
376            threadPool = (ThreadPoolExecutor) executorService;
377        } else if (executorService instanceof SizedScheduledExecutorService) {
378            threadPool = ((SizedScheduledExecutorService) executorService).getScheduledThreadPoolExecutor();
379        }
380        if (threadPool != null) {
381            for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
382                lifecycle.onThreadPoolRemove(camelContext, threadPool);
383            }
384        }
385
386        // remove reference as its shutdown (do not remove if fail-safe)
387        if (!failSafe) {
388            executorServices.remove(executorService);
389        }
390
391        return answer;
392    }
393
394    @Override
395    public boolean awaitTermination(ExecutorService executorService, long shutdownAwaitTermination) throws InterruptedException {
396        // log progress every 2nd second so end user is aware of we are shutting down
397        StopWatch watch = new StopWatch();
398        long interval = Math.min(2000, shutdownAwaitTermination);
399        boolean done = false;
400        while (!done && interval > 0) {
401            if (executorService.awaitTermination(interval, TimeUnit.MILLISECONDS)) {
402                done = true;
403            } else {
404                LOG.info("Waited {} for ExecutorService: {} to terminate...", TimeUtils.printDuration(watch.taken()), executorService);
405                // recalculate interval
406                interval = Math.min(2000, shutdownAwaitTermination - watch.taken());
407            }
408        }
409
410        return done;
411    }
412
413    /**
414     * Strategy callback when a new {@link java.util.concurrent.ExecutorService} have been created.
415     *
416     * @param executorService the created {@link java.util.concurrent.ExecutorService}
417     */
418    protected void onNewExecutorService(ExecutorService executorService) {
419        // noop
420    }
421
422    @Override
423    protected void doStart() throws Exception {
424        if (threadNamePattern == null) {
425            // set default name pattern which includes the camel context name
426            threadNamePattern = "Camel (" + camelContext.getName() + ") thread ##counter# - #name#";
427        }
428    }
429
430    @Override
431    protected void doStop() throws Exception {
432        // noop
433    }
434
435    @Override
436    protected void doShutdown() throws Exception {
437        // shutdown all remainder executor services by looping and doing this aggressively
438        // as by normal all threads pool should have been shutdown using proper lifecycle
439        // by their EIPs, components etc. This is acting as a fail-safe during shutdown
440        // of CamelContext itself.
441        Set<ExecutorService> forced = new LinkedHashSet<ExecutorService>();
442        if (!executorServices.isEmpty()) {
443            // at first give a bit of time to shutdown nicely as the thread pool is most likely in the process of being shutdown also
444            LOG.debug("Giving time for {} ExecutorService's to shutdown properly (acting as fail-safe)", executorServices.size());
445            for (ExecutorService executorService : executorServices) {
446                try {
447                    boolean warned = doShutdown(executorService, getShutdownAwaitTermination(), true);
448                    // remember the thread pools that was forced to shutdown (eg warned)
449                    if (warned) {
450                        forced.add(executorService);
451                    }
452                } catch (Throwable e) {
453                    // only log if something goes wrong as we want to shutdown them all
454                    LOG.warn("Error occurred during shutdown of ExecutorService: "
455                            + executorService + ". This exception will be ignored.", e);
456                }
457            }
458        }
459
460        // log the thread pools which was forced to shutdown so it may help the user to identify a problem of his
461        if (!forced.isEmpty()) {
462            LOG.warn("Forced shutdown of {} ExecutorService's which has not been shutdown properly (acting as fail-safe)", forced.size());
463            for (ExecutorService executorService : forced) {
464                LOG.warn("  forced -> {}", executorService);
465            }
466        }
467        forced.clear();
468
469        // clear list
470        executorServices.clear();
471
472        // do not clear the default profile as we could potential be restarted
473        Iterator<ThreadPoolProfile> it = threadPoolProfiles.values().iterator();
474        while (it.hasNext()) {
475            ThreadPoolProfile profile = it.next();
476            if (!profile.isDefaultProfile()) {
477                it.remove();
478            }
479        }
480    }
481
482    /**
483     * Invoked when a new thread pool is created.
484     * This implementation will invoke the {@link LifecycleStrategy#onThreadPoolAdd(org.apache.camel.CamelContext,
485     * java.util.concurrent.ThreadPoolExecutor, String, String, String, String) LifecycleStrategy.onThreadPoolAdd} method,
486     * which for example will enlist the thread pool in JMX management.
487     *
488     * @param executorService the thread pool
489     * @param source          the source to use the thread pool
490     * @param threadPoolProfileId profile id, if the thread pool was created from a thread pool profile
491     */
492    private void onThreadPoolCreated(ExecutorService executorService, Object source, String threadPoolProfileId) {
493        // add to internal list of thread pools
494        executorServices.add(executorService);
495
496        String id;
497        String sourceId = null;
498        String routeId = null;
499
500        // extract id from source
501        if (source instanceof NamedNode) {
502            id = ((OptionalIdentifiedDefinition<?>) source).idOrCreate(this.camelContext.getNodeIdFactory());
503            // and let source be the short name of the pattern
504            sourceId = ((NamedNode) source).getShortName();
505        } else if (source instanceof String) {
506            id = (String) source;
507        } else if (source != null) {
508            if (source instanceof StaticService) {
509                // the source is static service so its name would be unique
510                id = source.getClass().getSimpleName();
511            } else {
512                // fallback and use the simple class name with hashcode for the id so its unique for this given source
513                id = source.getClass().getSimpleName() + "(" + ObjectHelper.getIdentityHashCode(source) + ")";
514            }
515        } else {
516            // no source, so fallback and use the simple class name from thread pool and its hashcode identity so its unique
517            id = executorService.getClass().getSimpleName() + "(" + ObjectHelper.getIdentityHashCode(executorService) + ")";
518        }
519
520        // id is mandatory
521        ObjectHelper.notEmpty(id, "id for thread pool " + executorService);
522
523        // extract route id if possible
524        if (source instanceof ProcessorDefinition) {
525            RouteDefinition route = ProcessorDefinitionHelper.getRoute((ProcessorDefinition<?>) source);
526            if (route != null) {
527                routeId = route.idOrCreate(this.camelContext.getNodeIdFactory());
528            }
529        }
530
531        // let lifecycle strategy be notified as well which can let it be managed in JMX as well
532        ThreadPoolExecutor threadPool = null;
533        if (executorService instanceof ThreadPoolExecutor) {
534            threadPool = (ThreadPoolExecutor) executorService;
535        } else if (executorService instanceof SizedScheduledExecutorService) {
536            threadPool = ((SizedScheduledExecutorService) executorService).getScheduledThreadPoolExecutor();
537        }
538        if (threadPool != null) {
539            for (LifecycleStrategy lifecycle : camelContext.getLifecycleStrategies()) {
540                lifecycle.onThreadPoolAdd(camelContext, threadPool, id, sourceId, routeId, threadPoolProfileId);
541            }
542        }
543
544        // now call strategy to allow custom logic
545        onNewExecutorService(executorService);
546    }
547
548    private ThreadFactory createThreadFactory(String name, boolean isDaemon) {
549        ThreadFactory threadFactory = new CamelThreadFactory(threadNamePattern, name, isDaemon);
550        return threadFactory;
551    }
552
553}