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.StringHelper; 048import org.apache.camel.util.TimeUtils; 049import org.apache.camel.util.URISupport; 050import org.apache.camel.util.concurrent.CamelThreadFactory; 051import org.apache.camel.util.concurrent.SizedScheduledExecutorService; 052import org.apache.camel.util.concurrent.ThreadHelper; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 * Default {@link org.apache.camel.spi.ExecutorServiceManager}. 058 * 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<>(); 066 private String threadNamePattern; 067 private long shutdownAwaitTermination = 10000; 068 private String defaultThreadPoolProfileId = "defaultThreadPoolProfile"; 069 private final Map<String, ThreadPoolProfile> threadPoolProfiles = new ConcurrentHashMap<>(); 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.setAllowCoreThreadTimeOut(false); 083 defaultProfile.setRejectedPolicy(ThreadPoolRejectedPolicy.CallerRuns); 084 085 registerThreadPoolProfile(defaultProfile); 086 } 087 088 @Override 089 public ThreadPoolFactory getThreadPoolFactory() { 090 return threadPoolFactory; 091 } 092 093 @Override 094 public void setThreadPoolFactory(ThreadPoolFactory threadPoolFactory) { 095 this.threadPoolFactory = threadPoolFactory; 096 } 097 098 @Override 099 public void registerThreadPoolProfile(ThreadPoolProfile profile) { 100 ObjectHelper.notNull(profile, "profile"); 101 StringHelper.notEmpty(profile.getId(), "id", profile); 102 threadPoolProfiles.put(profile.getId(), profile); 103 } 104 105 @Override 106 public ThreadPoolProfile getThreadPoolProfile(String id) { 107 return threadPoolProfiles.get(id); 108 } 109 110 @Override 111 public ThreadPoolProfile getDefaultThreadPoolProfile() { 112 return getThreadPoolProfile(defaultThreadPoolProfileId); 113 } 114 115 @Override 116 public void setDefaultThreadPoolProfile(ThreadPoolProfile defaultThreadPoolProfile) { 117 threadPoolProfiles.remove(defaultThreadPoolProfileId); 118 defaultThreadPoolProfile.addDefaults(defaultProfile); 119 120 LOG.info("Using custom DefaultThreadPoolProfile: {}", defaultThreadPoolProfile); 121 122 this.defaultThreadPoolProfileId = defaultThreadPoolProfile.getId(); 123 defaultThreadPoolProfile.setDefaultProfile(true); 124 registerThreadPoolProfile(defaultThreadPoolProfile); 125 } 126 127 @Override 128 public String getThreadNamePattern() { 129 return threadNamePattern; 130 } 131 132 @Override 133 public void setThreadNamePattern(String threadNamePattern) { 134 // must set camel id here in the pattern and let the other placeholders be resolved on demand 135 this.threadNamePattern = threadNamePattern.replaceFirst("#camelId#", this.camelContext.getName()); 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: {}. -> {}", 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: {}. -> {}", 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: {} -> {}", 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 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 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 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<>(); 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 StringHelper.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 return new CamelThreadFactory(threadNamePattern, name, isDaemon); 550 } 551 552}