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}