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