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