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.activemq.broker.region.cursors; 018 019import java.util.Iterator; 020import java.util.LinkedList; 021import java.util.ListIterator; 022import java.util.concurrent.CancellationException; 023import java.util.concurrent.ExecutionException; 024import java.util.concurrent.Future; 025import java.util.concurrent.TimeUnit; 026import java.util.concurrent.TimeoutException; 027 028import org.apache.activemq.broker.region.Destination; 029import org.apache.activemq.broker.region.MessageReference; 030import org.apache.activemq.broker.region.Subscription; 031import org.apache.activemq.command.Message; 032import org.apache.activemq.command.MessageId; 033import org.apache.activemq.store.MessageRecoveryListener; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * Store based cursor 039 * 040 */ 041public abstract class AbstractStoreCursor extends AbstractPendingMessageCursor implements MessageRecoveryListener { 042 private static final Logger LOG = LoggerFactory.getLogger(AbstractStoreCursor.class); 043 protected final Destination regionDestination; 044 protected final PendingList batchList; 045 private Iterator<MessageReference> iterator = null; 046 protected boolean batchResetNeeded = false; 047 protected int size; 048 private final LinkedList<MessageId> pendingCachedIds = new LinkedList<>(); 049 private static int SYNC_ADD = 0; 050 private static int ASYNC_ADD = 1; 051 final MessageId[] lastCachedIds = new MessageId[2]; 052 protected boolean hadSpace = false; 053 054 055 056 protected AbstractStoreCursor(Destination destination) { 057 super((destination != null ? destination.isPrioritizedMessages():false)); 058 this.regionDestination=destination; 059 if (this.prioritizedMessages) { 060 this.batchList= new PrioritizedPendingList(); 061 } else { 062 this.batchList = new OrderedPendingList(); 063 } 064 } 065 066 067 @Override 068 public final synchronized void start() throws Exception{ 069 if (!isStarted()) { 070 super.start(); 071 resetBatch(); 072 resetSize(); 073 setCacheEnabled(size==0&&useCache); 074 } 075 } 076 077 protected void resetSize() { 078 this.size = getStoreSize(); 079 } 080 081 @Override 082 public void rebase() { 083 MessageId lastAdded = lastCachedIds[SYNC_ADD]; 084 if (lastAdded != null) { 085 try { 086 setBatch(lastAdded); 087 } catch (Exception e) { 088 LOG.error("{} - Failed to set batch on rebase", this, e); 089 throw new RuntimeException(e); 090 } 091 } 092 } 093 094 @Override 095 public final synchronized void stop() throws Exception { 096 resetBatch(); 097 super.stop(); 098 gc(); 099 } 100 101 102 @Override 103 public final boolean recoverMessage(Message message) throws Exception { 104 return recoverMessage(message,false); 105 } 106 107 public synchronized boolean recoverMessage(Message message, boolean cached) throws Exception { 108 boolean recovered = false; 109 message.setRegionDestination(regionDestination); 110 if (recordUniqueId(message.getMessageId())) { 111 if (!cached) { 112 if( message.getMemoryUsage()==null ) { 113 message.setMemoryUsage(this.getSystemUsage().getMemoryUsage()); 114 } 115 } 116 message.incrementReferenceCount(); 117 batchList.addMessageLast(message); 118 clearIterator(true); 119 recovered = true; 120 } else if (!cached) { 121 // a duplicate from the store (!cached) - needs to be removed/acked - otherwise it will get re dispatched on restart 122 if (duplicateFromStoreExcepted(message)) { 123 if (LOG.isTraceEnabled()) { 124 LOG.trace("{} store replayed pending message due to concurrentStoreAndDispatchQueues {} seq: {}", this, message.getMessageId(), message.getMessageId().getFutureOrSequenceLong()); 125 } 126 } else { 127 LOG.warn("{} - cursor got duplicate from store {} seq: {}", this, message.getMessageId(), message.getMessageId().getFutureOrSequenceLong()); 128 duplicate(message); 129 } 130 } else { 131 LOG.warn("{} - cursor got duplicate send {} seq: {}", this, message.getMessageId(), message.getMessageId().getFutureOrSequenceLong()); 132 if (gotToTheStore(message)) { 133 duplicate(message); 134 } 135 } 136 return recovered; 137 } 138 139 protected boolean duplicateFromStoreExcepted(Message message) { 140 // expected for messages pending acks with kahadb.concurrentStoreAndDispatchQueues=true for 141 // which this existing unused flag has been repurposed 142 return message.isRecievedByDFBridge(); 143 } 144 145 public static boolean gotToTheStore(Message message) throws Exception { 146 if (message.isRecievedByDFBridge()) { 147 // concurrent store and dispatch - wait to see if the message gets to the store to see 148 // if the index suppressed it (original still present), or whether it was stored and needs to be removed 149 Object possibleFuture = message.getMessageId().getFutureOrSequenceLong(); 150 if (possibleFuture instanceof Future) { 151 try { 152 ((Future) possibleFuture).get(); 153 } catch (Exception okToErrorOrCancelStoreOp) {} 154 } 155 // need to access again after wait on future 156 Object sequence = message.getMessageId().getFutureOrSequenceLong(); 157 return (sequence != null && sequence instanceof Long && Long.compare((Long) sequence, -1l) != 0); 158 } 159 return true; 160 } 161 162 // track for processing outside of store index lock so we can dlq 163 final LinkedList<Message> duplicatesFromStore = new LinkedList<Message>(); 164 private void duplicate(Message message) { 165 duplicatesFromStore.add(message); 166 } 167 168 void dealWithDuplicates() { 169 for (Message message : duplicatesFromStore) { 170 regionDestination.duplicateFromStore(message, getSubscription()); 171 } 172 duplicatesFromStore.clear(); 173 } 174 175 @Override 176 public final synchronized void reset() { 177 if (batchList.isEmpty()) { 178 try { 179 fillBatch(); 180 } catch (Exception e) { 181 LOG.error("{} - Failed to fill batch", this, e); 182 throw new RuntimeException(e); 183 } 184 } 185 clearIterator(true); 186 size(); 187 } 188 189 190 @Override 191 public synchronized void release() { 192 clearIterator(false); 193 } 194 195 private synchronized void clearIterator(boolean ensureIterator) { 196 boolean haveIterator = this.iterator != null; 197 this.iterator=null; 198 if(haveIterator&&ensureIterator) { 199 ensureIterator(); 200 } 201 } 202 203 private synchronized void ensureIterator() { 204 if(this.iterator==null) { 205 this.iterator=this.batchList.iterator(); 206 } 207 } 208 209 210 public final void finished() { 211 } 212 213 214 @Override 215 public final synchronized boolean hasNext() { 216 if (batchList.isEmpty()) { 217 try { 218 fillBatch(); 219 } catch (Exception e) { 220 LOG.error("{} - Failed to fill batch", this, e); 221 throw new RuntimeException(e); 222 } 223 } 224 ensureIterator(); 225 return this.iterator.hasNext(); 226 } 227 228 229 @Override 230 public final synchronized MessageReference next() { 231 MessageReference result = null; 232 if (!this.batchList.isEmpty()&&this.iterator.hasNext()) { 233 result = this.iterator.next(); 234 } 235 last = result; 236 if (result != null) { 237 result.incrementReferenceCount(); 238 } 239 return result; 240 } 241 242 @Override 243 public synchronized boolean tryAddMessageLast(MessageReference node, long wait) throws Exception { 244 boolean disableCache = false; 245 if (hasSpace()) { 246 if (isCacheEnabled()) { 247 if (recoverMessage(node.getMessage(),true)) { 248 trackLastCached(node); 249 } else { 250 dealWithDuplicates(); 251 return false; 252 } 253 } 254 } else { 255 disableCache = true; 256 } 257 258 if (disableCache && isCacheEnabled()) { 259 if (LOG.isTraceEnabled()) { 260 LOG.trace("{} - disabling cache on add {} {}", this, node.getMessageId(), node.getMessageId().getFutureOrSequenceLong()); 261 } 262 syncWithStore(node.getMessage()); 263 setCacheEnabled(false); 264 } 265 size++; 266 return true; 267 } 268 269 @Override 270 public synchronized boolean isCacheEnabled() { 271 return super.isCacheEnabled() || enableCacheNow(); 272 } 273 274 protected boolean enableCacheNow() { 275 boolean result = false; 276 if (canEnableCash()) { 277 setCacheEnabled(true); 278 result = true; 279 if (LOG.isTraceEnabled()) { 280 LOG.trace("{} enabling cache on empty store", this); 281 } 282 } 283 return result; 284 } 285 286 protected boolean canEnableCash() { 287 return useCache && size==0 && hasSpace() && isStarted(); 288 } 289 290 @Override 291 public boolean canRecoveryNextMessage() { 292 // Should be safe to recovery messages if the overall memory usage if < 90% 293 return parentHasSpace(90); 294 } 295 296 private void syncWithStore(Message currentAdd) throws Exception { 297 pruneLastCached(); 298 for (ListIterator<MessageId> it = pendingCachedIds.listIterator(pendingCachedIds.size()); it.hasPrevious(); ) { 299 MessageId lastPending = it.previous(); 300 Object futureOrLong = lastPending.getFutureOrSequenceLong(); 301 if (futureOrLong instanceof Future) { 302 Future future = (Future) futureOrLong; 303 if (future.isCancelled()) { 304 continue; 305 } 306 try { 307 future.get(5, TimeUnit.SECONDS); 308 setLastCachedId(ASYNC_ADD, lastPending); 309 } catch (CancellationException ok) { 310 continue; 311 } catch (TimeoutException potentialDeadlock) { 312 LOG.debug("{} timed out waiting for async add", this, potentialDeadlock); 313 } catch (Exception worstCaseWeReplay) { 314 LOG.debug("{} exception waiting for async add", this, worstCaseWeReplay); 315 } 316 } else { 317 setLastCachedId(ASYNC_ADD, lastPending); 318 } 319 break; 320 } 321 322 MessageId candidate = lastCachedIds[ASYNC_ADD]; 323 if (candidate != null) { 324 // ensure we don't skip current possibly sync add b/c we waited on the future 325 if (!isAsync(currentAdd) && Long.compare(((Long) currentAdd.getMessageId().getFutureOrSequenceLong()), ((Long) lastCachedIds[ASYNC_ADD].getFutureOrSequenceLong())) < 0) { 326 if (LOG.isTraceEnabled()) { 327 LOG.trace("no set batch from async:" + candidate.getFutureOrSequenceLong() + " >= than current: " + currentAdd.getMessageId().getFutureOrSequenceLong() + ", " + this); 328 } 329 candidate = null; 330 } 331 } 332 if (candidate == null) { 333 candidate = lastCachedIds[SYNC_ADD]; 334 } 335 if (candidate != null) { 336 setBatch(candidate); 337 } 338 // cleanup 339 lastCachedIds[SYNC_ADD] = lastCachedIds[ASYNC_ADD] = null; 340 pendingCachedIds.clear(); 341 } 342 343 private void trackLastCached(MessageReference node) { 344 if (isAsync(node.getMessage())) { 345 pruneLastCached(); 346 pendingCachedIds.add(node.getMessageId()); 347 } else { 348 setLastCachedId(SYNC_ADD, node.getMessageId()); 349 } 350 } 351 352 private static final boolean isAsync(Message message) { 353 return message.isRecievedByDFBridge() || message.getMessageId().getFutureOrSequenceLong() instanceof Future; 354 } 355 356 private void pruneLastCached() { 357 for (Iterator<MessageId> it = pendingCachedIds.iterator(); it.hasNext(); ) { 358 MessageId candidate = it.next(); 359 final Object futureOrLong = candidate.getFutureOrSequenceLong(); 360 if (futureOrLong instanceof Future) { 361 Future future = (Future) futureOrLong; 362 if (future.isDone()) { 363 if (future.isCancelled()) { 364 it.remove(); 365 } else { 366 // check for exception, we may be seeing old state 367 try { 368 future.get(0, TimeUnit.SECONDS); 369 // stale; if we get a result next prune will see Long 370 } catch (ExecutionException expected) { 371 it.remove(); 372 } catch (Exception unexpected) { 373 LOG.debug("{} unexpected exception verifying exception state of future", this, unexpected); 374 } 375 } 376 } else { 377 // we don't want to wait for work to complete 378 break; 379 } 380 } else { 381 // complete 382 setLastCachedId(ASYNC_ADD, candidate); 383 384 // keep lock step with sync adds while order is preserved 385 if (lastCachedIds[SYNC_ADD] != null) { 386 long next = 1 + (Long)lastCachedIds[SYNC_ADD].getFutureOrSequenceLong(); 387 if (Long.compare((Long)futureOrLong, next) == 0) { 388 setLastCachedId(SYNC_ADD, candidate); 389 } 390 } 391 it.remove(); 392 } 393 } 394 } 395 396 private void setLastCachedId(final int index, MessageId candidate) { 397 MessageId lastCacheId = lastCachedIds[index]; 398 if (lastCacheId == null) { 399 lastCachedIds[index] = candidate; 400 } else { 401 Object lastCacheFutureOrSequenceLong = lastCacheId.getFutureOrSequenceLong(); 402 Object candidateOrSequenceLong = candidate.getFutureOrSequenceLong(); 403 if (lastCacheFutureOrSequenceLong == null) { // possibly null for topics 404 lastCachedIds[index] = candidate; 405 } else if (candidateOrSequenceLong != null && 406 Long.compare(((Long) candidateOrSequenceLong), ((Long) lastCacheFutureOrSequenceLong)) > 0) { 407 lastCachedIds[index] = candidate; 408 } else if (LOG.isTraceEnabled()) { 409 LOG.trace("no set last cached[" + index + "] current:" + lastCacheFutureOrSequenceLong + " <= than candidate: " + candidateOrSequenceLong+ ", " + this); 410 } 411 } 412 } 413 414 protected void setBatch(MessageId messageId) throws Exception { 415 } 416 417 418 @Override 419 public synchronized void addMessageFirst(MessageReference node) throws Exception { 420 size++; 421 } 422 423 424 @Override 425 public final synchronized void remove() { 426 size--; 427 if (iterator!=null) { 428 iterator.remove(); 429 } 430 if (last != null) { 431 last.decrementReferenceCount(); 432 } 433 } 434 435 436 @Override 437 public final synchronized void remove(MessageReference node) { 438 if (batchList.remove(node) != null) { 439 size--; 440 setCacheEnabled(false); 441 } 442 } 443 444 445 @Override 446 public final synchronized void clear() { 447 gc(); 448 } 449 450 451 @Override 452 public synchronized void gc() { 453 for (MessageReference msg : batchList) { 454 rollback(msg.getMessageId()); 455 msg.decrementReferenceCount(); 456 } 457 batchList.clear(); 458 clearIterator(false); 459 batchResetNeeded = true; 460 setCacheEnabled(false); 461 } 462 463 @Override 464 protected final synchronized void fillBatch() { 465 if (LOG.isTraceEnabled()) { 466 LOG.trace("{} fillBatch", this); 467 } 468 if (batchResetNeeded) { 469 resetSize(); 470 setMaxBatchSize(Math.min(regionDestination.getMaxPageSize(), size)); 471 resetBatch(); 472 this.batchResetNeeded = false; 473 } 474 if (this.batchList.isEmpty() && this.size >0) { 475 try { 476 doFillBatch(); 477 } catch (Exception e) { 478 LOG.error("{} - Failed to fill batch", this, e); 479 throw new RuntimeException(e); 480 } 481 } 482 } 483 484 485 @Override 486 public final synchronized boolean isEmpty() { 487 // negative means more messages added to store through queue.send since last reset 488 return size == 0; 489 } 490 491 492 @Override 493 public final synchronized boolean hasMessagesBufferedToDeliver() { 494 return !batchList.isEmpty(); 495 } 496 497 498 @Override 499 public final synchronized int size() { 500 if (size < 0) { 501 this.size = getStoreSize(); 502 } 503 return size; 504 } 505 506 @Override 507 public final synchronized long messageSize() { 508 return getStoreMessageSize(); 509 } 510 511 @Override 512 public String toString() { 513 return super.toString() + ":" + regionDestination.getActiveMQDestination().getPhysicalName() + ",batchResetNeeded=" + batchResetNeeded 514 + ",size=" + this.size + ",cacheEnabled=" + cacheEnabled 515 + ",maxBatchSize:" + maxBatchSize + ",hasSpace:" + hasSpace() + ",pendingCachedIds.size:" + pendingCachedIds.size() 516 + ",lastSyncCachedId:" + lastCachedIds[SYNC_ADD] + ",lastSyncCachedId-seq:" + (lastCachedIds[SYNC_ADD] != null ? lastCachedIds[SYNC_ADD].getFutureOrSequenceLong() : "null") 517 + ",lastAsyncCachedId:" + lastCachedIds[ASYNC_ADD] + ",lastAsyncCachedId-seq:" + (lastCachedIds[ASYNC_ADD] != null ? lastCachedIds[ASYNC_ADD].getFutureOrSequenceLong() : "null"); 518 } 519 520 protected abstract void doFillBatch() throws Exception; 521 522 protected abstract void resetBatch(); 523 524 protected abstract int getStoreSize(); 525 526 protected abstract long getStoreMessageSize(); 527 528 protected abstract boolean isStoreEmpty(); 529 530 public Subscription getSubscription() { 531 return null; 532 } 533}