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