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.Collection; 020import java.util.Collections; 021import java.util.LinkedList; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.CountDownLatch; 025import java.util.concurrent.RejectedExecutionException; 026import java.util.concurrent.atomic.AtomicLong; 027 028import org.apache.camel.Exchange; 029import org.apache.camel.MessageHistory; 030import org.apache.camel.NamedNode; 031import org.apache.camel.processor.DefaultExchangeFormatter; 032import org.apache.camel.spi.AsyncProcessorAwaitManager; 033import org.apache.camel.spi.ExchangeFormatter; 034import org.apache.camel.support.ServiceSupport; 035import org.apache.camel.util.MessageHelper; 036import org.apache.camel.util.ObjectHelper; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040public class DefaultAsyncProcessorAwaitManager extends ServiceSupport implements AsyncProcessorAwaitManager { 041 042 private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncProcessorAwaitManager.class); 043 044 private final AsyncProcessorAwaitManager.Statistics statistics = new UtilizationStatistics(); 045 private final AtomicLong blockedCounter = new AtomicLong(); 046 private final AtomicLong interruptedCounter = new AtomicLong(); 047 private final AtomicLong totalDuration = new AtomicLong(); 048 private final AtomicLong minDuration = new AtomicLong(); 049 private final AtomicLong maxDuration = new AtomicLong(); 050 private final AtomicLong meanDuration = new AtomicLong(); 051 052 private final Map<Exchange, AwaitThread> inflight = new ConcurrentHashMap<>(); 053 private final ExchangeFormatter exchangeFormatter; 054 private boolean interruptThreadsWhileStopping = true; 055 056 public DefaultAsyncProcessorAwaitManager() { 057 // setup exchange formatter to be used for message history dump 058 DefaultExchangeFormatter formatter = new DefaultExchangeFormatter(); 059 formatter.setShowExchangeId(true); 060 formatter.setMultiline(true); 061 formatter.setShowHeaders(true); 062 formatter.setStyle(DefaultExchangeFormatter.OutputStyle.Fixed); 063 this.exchangeFormatter = formatter; 064 } 065 066 @Override 067 public void await(Exchange exchange, CountDownLatch latch) { 068 LOG.trace("Waiting for asynchronous callback before continuing for exchangeId: {} -> {}", 069 exchange.getExchangeId(), exchange); 070 try { 071 if (statistics.isStatisticsEnabled()) { 072 blockedCounter.incrementAndGet(); 073 } 074 inflight.put(exchange, new AwaitThreadEntry(Thread.currentThread(), exchange, latch)); 075 latch.await(); 076 LOG.trace("Asynchronous callback received, will continue routing exchangeId: {} -> {}", 077 exchange.getExchangeId(), exchange); 078 079 } catch (InterruptedException e) { 080 LOG.trace("Interrupted while waiting for callback, will continue routing exchangeId: {} -> {}", 081 exchange.getExchangeId(), exchange); 082 exchange.setException(e); 083 } finally { 084 AwaitThread thread = inflight.remove(exchange); 085 086 if (statistics.isStatisticsEnabled() && thread != null) { 087 long time = thread.getWaitDuration(); 088 long total = totalDuration.get() + time; 089 totalDuration.set(total); 090 091 if (time < minDuration.get()) { 092 minDuration.set(time); 093 } else if (time > maxDuration.get()) { 094 maxDuration.set(time); 095 } 096 097 // update mean 098 long count = blockedCounter.get(); 099 long mean = count > 0 ? total / count : 0; 100 meanDuration.set(mean); 101 } 102 } 103 } 104 105 @Override 106 public void countDown(Exchange exchange, CountDownLatch latch) { 107 LOG.trace("Asynchronous callback received for exchangeId: {}", exchange.getExchangeId()); 108 latch.countDown(); 109 } 110 111 @Override 112 public int size() { 113 return inflight.size(); 114 } 115 116 @Override 117 public Collection<AwaitThread> browse() { 118 return Collections.unmodifiableCollection(inflight.values()); 119 } 120 121 @Override 122 public void interrupt(String exchangeId) { 123 // need to find the exchange with the given exchange id 124 Exchange found = null; 125 for (AsyncProcessorAwaitManager.AwaitThread entry : browse()) { 126 Exchange exchange = entry.getExchange(); 127 if (exchangeId.equals(exchange.getExchangeId())) { 128 found = exchange; 129 break; 130 } 131 } 132 133 if (found != null) { 134 interrupt(found); 135 } 136 } 137 138 @Override 139 public void interrupt(Exchange exchange) { 140 AwaitThreadEntry entry = (AwaitThreadEntry) inflight.get(exchange); 141 if (entry != null) { 142 try { 143 StringBuilder sb = new StringBuilder(); 144 sb.append("Interrupted while waiting for asynchronous callback, will release the following blocked thread which was waiting for exchange to finish processing with exchangeId: "); 145 sb.append(exchange.getExchangeId()); 146 sb.append("\n"); 147 148 sb.append(dumpBlockedThread(entry)); 149 150 // dump a route stack trace of the exchange 151 String routeStackTrace = MessageHelper.dumpMessageHistoryStacktrace(exchange, exchangeFormatter, false); 152 if (routeStackTrace != null) { 153 sb.append(routeStackTrace); 154 } 155 LOG.warn(sb.toString()); 156 157 } catch (Exception e) { 158 throw ObjectHelper.wrapRuntimeCamelException(e); 159 } finally { 160 if (statistics.isStatisticsEnabled()) { 161 interruptedCounter.incrementAndGet(); 162 } 163 exchange.setException(new RejectedExecutionException("Interrupted while waiting for asynchronous callback for exchangeId: " + exchange.getExchangeId())); 164 exchange.setProperty(Exchange.INTERRUPTED, Boolean.TRUE); 165 entry.getLatch().countDown(); 166 } 167 } 168 } 169 170 public boolean isInterruptThreadsWhileStopping() { 171 return interruptThreadsWhileStopping; 172 } 173 174 public void setInterruptThreadsWhileStopping(boolean interruptThreadsWhileStopping) { 175 this.interruptThreadsWhileStopping = interruptThreadsWhileStopping; 176 } 177 178 public Statistics getStatistics() { 179 return statistics; 180 } 181 182 @Override 183 protected void doStart() throws Exception { 184 // noop 185 } 186 187 @Override 188 protected void doStop() throws Exception { 189 Collection<AwaitThread> threads = browse(); 190 int count = threads.size(); 191 if (count > 0) { 192 LOG.warn("Shutting down while there are still {} inflight threads currently blocked.", count); 193 194 StringBuilder sb = new StringBuilder(); 195 for (AwaitThread entry : threads) { 196 sb.append(dumpBlockedThread(entry)); 197 } 198 199 if (isInterruptThreadsWhileStopping()) { 200 LOG.warn("The following threads are blocked and will be interrupted so the threads are released:\n" + sb.toString()); 201 for (AwaitThread entry : threads) { 202 try { 203 interrupt(entry.getExchange()); 204 } catch (Throwable e) { 205 LOG.warn("Error while interrupting thread: " + entry.getBlockedThread().getName() + ". This exception is ignored.", e); 206 } 207 } 208 } else { 209 LOG.warn("The following threads are blocked, and may reside in the JVM:\n" + sb.toString()); 210 } 211 } else { 212 LOG.debug("Shutting down with no inflight threads."); 213 } 214 215 inflight.clear(); 216 } 217 218 private static String dumpBlockedThread(AwaitThread entry) { 219 StringBuilder sb = new StringBuilder(); 220 sb.append("\n"); 221 sb.append("Blocked Thread\n"); 222 sb.append("---------------------------------------------------------------------------------------------------------------------------------------\n"); 223 224 sb.append(style("Id:")).append(entry.getBlockedThread().getId()).append("\n"); 225 sb.append(style("Name:")).append(entry.getBlockedThread().getName()).append("\n"); 226 sb.append(style("RouteId:")).append(safeNull(entry.getRouteId())).append("\n"); 227 sb.append(style("NodeId:")).append(safeNull(entry.getNodeId())).append("\n"); 228 sb.append(style("Duration:")).append(entry.getWaitDuration()).append(" msec.\n"); 229 return sb.toString(); 230 } 231 232 private static String style(String label) { 233 return String.format("\t%-20s", label); 234 } 235 236 private static String safeNull(Object value) { 237 return value != null ? value.toString() : ""; 238 } 239 240 private static final class AwaitThreadEntry implements AwaitThread { 241 private final Thread thread; 242 private final Exchange exchange; 243 private final CountDownLatch latch; 244 private final long start; 245 246 private AwaitThreadEntry(Thread thread, Exchange exchange, CountDownLatch latch) { 247 this.thread = thread; 248 this.exchange = exchange; 249 this.latch = latch; 250 this.start = System.currentTimeMillis(); 251 } 252 253 @Override 254 public Thread getBlockedThread() { 255 return thread; 256 } 257 258 @Override 259 public Exchange getExchange() { 260 return exchange; 261 } 262 263 @Override 264 public long getWaitDuration() { 265 return System.currentTimeMillis() - start; 266 } 267 268 @Override 269 public String getRouteId() { 270 MessageHistory lastMessageHistory = getLastMessageHistory(); 271 if (lastMessageHistory == null) { 272 return null; 273 } 274 return lastMessageHistory.getRouteId(); 275 } 276 277 @Override 278 public String getNodeId() { 279 NamedNode node = getNode(); 280 if (node == null) { 281 return null; 282 } 283 return node.getId(); 284 } 285 286 public CountDownLatch getLatch() { 287 return latch; 288 } 289 290 private NamedNode getNode() { 291 MessageHistory lastMessageHistory = getLastMessageHistory(); 292 if (lastMessageHistory == null) { 293 return null; 294 } 295 return lastMessageHistory.getNode(); 296 } 297 298 private MessageHistory getLastMessageHistory() { 299 LinkedList<MessageHistory> list = getMessageHistories(); 300 if (list == null || list.isEmpty()) { 301 return null; 302 } 303 return list.getLast(); 304 } 305 306 private LinkedList<MessageHistory> getMessageHistories() { 307 return exchange.getProperty(Exchange.MESSAGE_HISTORY, LinkedList.class); 308 } 309 310 @Override 311 public String toString() { 312 return "AwaitThreadEntry[name=" + thread.getName() + ", exchangeId=" + exchange.getExchangeId() + "]"; 313 } 314 } 315 316 /** 317 * Represents utilization statistics 318 */ 319 private final class UtilizationStatistics implements AsyncProcessorAwaitManager.Statistics { 320 321 private boolean statisticsEnabled; 322 323 @Override 324 public long getThreadsBlocked() { 325 return blockedCounter.get(); 326 } 327 328 @Override 329 public long getThreadsInterrupted() { 330 return interruptedCounter.get(); 331 } 332 333 @Override 334 public long getTotalDuration() { 335 return totalDuration.get(); 336 } 337 338 @Override 339 public long getMinDuration() { 340 return minDuration.get(); 341 } 342 343 @Override 344 public long getMaxDuration() { 345 return maxDuration.get(); 346 } 347 348 @Override 349 public long getMeanDuration() { 350 return meanDuration.get(); 351 } 352 353 @Override 354 public void reset() { 355 blockedCounter.set(0); 356 interruptedCounter.set(0); 357 totalDuration.set(0); 358 minDuration.set(0); 359 maxDuration.set(0); 360 meanDuration.set(0); 361 } 362 363 @Override 364 public boolean isStatisticsEnabled() { 365 return statisticsEnabled; 366 } 367 368 @Override 369 public void setStatisticsEnabled(boolean statisticsEnabled) { 370 this.statisticsEnabled = statisticsEnabled; 371 } 372 373 @Override 374 public String toString() { 375 return String.format("AsyncProcessAwaitManager utilization[blocked=%s, interrupted=%s, total=%s min=%s, max=%s, mean=%s]", 376 getThreadsBlocked(), getThreadsInterrupted(), getTotalDuration(), getMinDuration(), getMaxDuration(), getMeanDuration()); 377 } 378 } 379 380}