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.io.IOException; 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.LinkedList; 023import java.util.List; 024import java.util.concurrent.atomic.AtomicBoolean; 025import java.util.concurrent.atomic.AtomicLong; 026 027import org.apache.activemq.broker.Broker; 028import org.apache.activemq.broker.ConnectionContext; 029import org.apache.activemq.broker.region.Destination; 030import org.apache.activemq.broker.region.IndirectMessageReference; 031import org.apache.activemq.broker.region.MessageReference; 032import org.apache.activemq.broker.region.QueueMessageReference; 033import org.apache.activemq.command.Message; 034import org.apache.activemq.openwire.OpenWireFormat; 035import org.apache.activemq.store.PList; 036import org.apache.activemq.store.PListEntry; 037import org.apache.activemq.store.PListStore; 038import org.apache.activemq.usage.SystemUsage; 039import org.apache.activemq.usage.Usage; 040import org.apache.activemq.usage.UsageListener; 041import org.apache.activemq.util.ByteSequence; 042import org.apache.activemq.wireformat.WireFormat; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046/** 047 * persist pending messages pending message (messages awaiting dispatch to a 048 * consumer) cursor 049 */ 050public class FilePendingMessageCursor extends AbstractPendingMessageCursor implements UsageListener { 051 052 static final Logger LOG = LoggerFactory.getLogger(FilePendingMessageCursor.class); 053 054 private static final AtomicLong NAME_COUNT = new AtomicLong(); 055 056 protected Broker broker; 057 private final PListStore store; 058 private final String name; 059 private PendingList memoryList; 060 private PList diskList; 061 private Iterator<MessageReference> iter; 062 private Destination regionDestination; 063 private boolean iterating; 064 private boolean flushRequired; 065 private final AtomicBoolean started = new AtomicBoolean(); 066 private final WireFormat wireFormat = new OpenWireFormat(); 067 068 /** 069 * @param broker 070 * @param name 071 * @param prioritizedMessages 072 */ 073 public FilePendingMessageCursor(Broker broker, String name, boolean prioritizedMessages) { 074 super(prioritizedMessages); 075 if (this.prioritizedMessages) { 076 this.memoryList = new PrioritizedPendingList(); 077 } else { 078 this.memoryList = new OrderedPendingList(); 079 } 080 this.broker = broker; 081 // the store can be null if the BrokerService has persistence 082 // turned off 083 this.store = broker.getTempDataStore(); 084 this.name = NAME_COUNT.incrementAndGet() + "_" + name; 085 } 086 087 @Override 088 public void start() throws Exception { 089 if (started.compareAndSet(false, true)) { 090 if( this.broker != null) { 091 wireFormat.setVersion(this.broker.getBrokerService().getStoreOpenWireVersion()); 092 } 093 super.start(); 094 if (systemUsage != null) { 095 systemUsage.getMemoryUsage().addUsageListener(this); 096 } 097 } 098 } 099 100 @Override 101 public void stop() throws Exception { 102 if (started.compareAndSet(true, false)) { 103 super.stop(); 104 if (systemUsage != null) { 105 systemUsage.getMemoryUsage().removeUsageListener(this); 106 } 107 } 108 } 109 110 /** 111 * @return true if there are no pending messages 112 */ 113 @Override 114 public synchronized boolean isEmpty() { 115 if (memoryList.isEmpty() && isDiskListEmpty()) { 116 return true; 117 } 118 for (Iterator<MessageReference> iterator = memoryList.iterator(); iterator.hasNext();) { 119 MessageReference node = iterator.next(); 120 if (node == QueueMessageReference.NULL_MESSAGE) { 121 continue; 122 } 123 if (!node.isDropped()) { 124 return false; 125 } 126 // We can remove dropped references. 127 iterator.remove(); 128 } 129 return isDiskListEmpty(); 130 } 131 132 /** 133 * reset the cursor 134 */ 135 @Override 136 public synchronized void reset() { 137 iterating = true; 138 last = null; 139 if (isDiskListEmpty()) { 140 this.iter = this.memoryList.iterator(); 141 } else { 142 this.iter = new DiskIterator(); 143 } 144 } 145 146 @Override 147 public synchronized void release() { 148 iterating = false; 149 if (iter instanceof DiskIterator) { 150 ((DiskIterator)iter).release(); 151 }; 152 if (flushRequired) { 153 flushRequired = false; 154 if (!hasSpace()) { 155 flushToDisk(); 156 } 157 } 158 // ensure any memory ref is released 159 iter = null; 160 } 161 162 @Override 163 public synchronized void destroy() throws Exception { 164 stop(); 165 for (Iterator<MessageReference> i = memoryList.iterator(); i.hasNext();) { 166 MessageReference node = i.next(); 167 node.decrementReferenceCount(); 168 } 169 memoryList.clear(); 170 destroyDiskList(); 171 } 172 173 private void destroyDiskList() throws Exception { 174 if (diskList != null) { 175 store.removePList(name); 176 diskList = null; 177 } 178 } 179 180 @Override 181 public synchronized LinkedList<MessageReference> pageInList(int maxItems) { 182 LinkedList<MessageReference> result = new LinkedList<MessageReference>(); 183 int count = 0; 184 for (Iterator<MessageReference> i = memoryList.iterator(); i.hasNext() && count < maxItems;) { 185 MessageReference ref = i.next(); 186 ref.incrementReferenceCount(); 187 result.add(ref); 188 count++; 189 } 190 if (count < maxItems && !isDiskListEmpty()) { 191 for (Iterator<MessageReference> i = new DiskIterator(); i.hasNext() && count < maxItems;) { 192 Message message = (Message) i.next(); 193 message.setRegionDestination(regionDestination); 194 message.setMemoryUsage(this.getSystemUsage().getMemoryUsage()); 195 message.incrementReferenceCount(); 196 result.add(message); 197 count++; 198 } 199 } 200 return result; 201 } 202 203 /** 204 * add message to await dispatch 205 * 206 * @param node 207 * @throws Exception 208 */ 209 @Override 210 public synchronized boolean tryAddMessageLast(MessageReference node, long maxWaitTime) throws Exception { 211 if (!node.isExpired()) { 212 try { 213 regionDestination = (Destination) node.getMessage().getRegionDestination(); 214 if (isDiskListEmpty()) { 215 if (hasSpace() || this.store == null) { 216 memoryList.addMessageLast(node); 217 node.incrementReferenceCount(); 218 setCacheEnabled(true); 219 return true; 220 } 221 } 222 if (!hasSpace()) { 223 if (isDiskListEmpty()) { 224 expireOldMessages(); 225 if (hasSpace()) { 226 memoryList.addMessageLast(node); 227 node.incrementReferenceCount(); 228 return true; 229 } else { 230 flushToDisk(); 231 } 232 } 233 } 234 if (systemUsage.getTempUsage().waitForSpace(maxWaitTime)) { 235 ByteSequence bs = getByteSequence(node.getMessage()); 236 getDiskList().addLast(node.getMessageId().toString(), bs); 237 return true; 238 } 239 return false; 240 241 } catch (Exception e) { 242 LOG.error("Caught an Exception adding a message: {} first to FilePendingMessageCursor ", node, e); 243 throw new RuntimeException(e); 244 } 245 } else { 246 discardExpiredMessage(node); 247 } 248 //message expired 249 return true; 250 } 251 252 /** 253 * add message to await dispatch 254 * 255 * @param node 256 */ 257 @Override 258 public synchronized void addMessageFirst(MessageReference node) { 259 if (!node.isExpired()) { 260 try { 261 regionDestination = (Destination) node.getMessage().getRegionDestination(); 262 if (isDiskListEmpty()) { 263 if (hasSpace()) { 264 memoryList.addMessageFirst(node); 265 node.incrementReferenceCount(); 266 setCacheEnabled(true); 267 return; 268 } 269 } 270 if (!hasSpace()) { 271 if (isDiskListEmpty()) { 272 expireOldMessages(); 273 if (hasSpace()) { 274 memoryList.addMessageFirst(node); 275 node.incrementReferenceCount(); 276 return; 277 } else { 278 flushToDisk(); 279 } 280 } 281 } 282 systemUsage.getTempUsage().waitForSpace(); 283 node.decrementReferenceCount(); 284 ByteSequence bs = getByteSequence(node.getMessage()); 285 Object locator = getDiskList().addFirst(node.getMessageId().toString(), bs); 286 node.getMessageId().setPlistLocator(locator); 287 288 } catch (Exception e) { 289 LOG.error("Caught an Exception adding a message: {} first to FilePendingMessageCursor ", node, e); 290 throw new RuntimeException(e); 291 } 292 } else { 293 discardExpiredMessage(node); 294 } 295 } 296 297 /** 298 * @return true if there pending messages to dispatch 299 */ 300 @Override 301 public synchronized boolean hasNext() { 302 return iter.hasNext(); 303 } 304 305 /** 306 * @return the next pending message 307 */ 308 @Override 309 public synchronized MessageReference next() { 310 MessageReference reference = iter.next(); 311 last = reference; 312 if (!isDiskListEmpty()) { 313 // got from disk 314 reference.getMessage().setRegionDestination(regionDestination); 315 reference.getMessage().setMemoryUsage(this.getSystemUsage().getMemoryUsage()); 316 } 317 reference.incrementReferenceCount(); 318 return reference; 319 } 320 321 /** 322 * remove the message at the cursor position 323 */ 324 @Override 325 public synchronized void remove() { 326 iter.remove(); 327 if (last != null) { 328 last.decrementReferenceCount(); 329 } 330 } 331 332 /** 333 * @param node 334 * @see org.apache.activemq.broker.region.cursors.AbstractPendingMessageCursor#remove(org.apache.activemq.broker.region.MessageReference) 335 */ 336 @Override 337 public synchronized void remove(MessageReference node) { 338 if (memoryList.remove(node) != null) { 339 node.decrementReferenceCount(); 340 } 341 if (!isDiskListEmpty()) { 342 try { 343 getDiskList().remove(node.getMessageId().getPlistLocator()); 344 } catch (IOException e) { 345 throw new RuntimeException(e); 346 } 347 } 348 } 349 350 /** 351 * @return the number of pending messages 352 */ 353 @Override 354 public synchronized int size() { 355 return memoryList.size() + (isDiskListEmpty() ? 0 : (int)getDiskList().size()); 356 } 357 358 @Override 359 public synchronized long messageSize() { 360 return memoryList.messageSize() + (isDiskListEmpty() ? 0 : getDiskList().messageSize()); 361 } 362 363 /** 364 * clear all pending messages 365 */ 366 @Override 367 public synchronized void clear() { 368 memoryList.clear(); 369 if (!isDiskListEmpty()) { 370 try { 371 getDiskList().destroy(); 372 } catch (IOException e) { 373 throw new RuntimeException(e); 374 } 375 } 376 last = null; 377 } 378 379 @Override 380 public synchronized boolean isFull() { 381 return super.isFull() || (!isDiskListEmpty() && systemUsage != null && systemUsage.getTempUsage().isFull()); 382 } 383 384 @Override 385 public boolean hasMessagesBufferedToDeliver() { 386 return !isEmpty(); 387 } 388 389 @Override 390 public void setSystemUsage(SystemUsage usageManager) { 391 super.setSystemUsage(usageManager); 392 } 393 394 @Override 395 public void onUsageChanged(Usage usage, int oldPercentUsage, int newPercentUsage) { 396 if (newPercentUsage >= getMemoryUsageHighWaterMark()) { 397 List<MessageReference> expiredMessages = null; 398 synchronized (this) { 399 if (!flushRequired && size() != 0) { 400 flushRequired =true; 401 if (!iterating) { 402 expiredMessages = expireOldMessages(); 403 if (!hasSpace()) { 404 flushToDisk(); 405 flushRequired = false; 406 } 407 } 408 } 409 } 410 411 if (expiredMessages != null) { 412 for (MessageReference node : expiredMessages) { 413 discardExpiredMessage(node); 414 } 415 } 416 } 417 } 418 419 @Override 420 public boolean isTransient() { 421 return true; 422 } 423 424 private synchronized List<MessageReference> expireOldMessages() { 425 List<MessageReference> expired = new ArrayList<MessageReference>(); 426 if (!memoryList.isEmpty()) { 427 for (Iterator<MessageReference> iterator = memoryList.iterator(); iterator.hasNext();) { 428 MessageReference node = iterator.next(); 429 if (node.isExpired()) { 430 node.decrementReferenceCount(); 431 expired.add(node); 432 iterator.remove(); 433 } 434 } 435 } 436 437 return expired; 438 } 439 440 protected synchronized void flushToDisk() { 441 if (!memoryList.isEmpty() && store != null) { 442 long start = 0; 443 if (LOG.isTraceEnabled()) { 444 start = System.currentTimeMillis(); 445 LOG.trace("{}, flushToDisk() mem list size: {} {}", 446 name, memoryList.size(), 447 (systemUsage != null ? systemUsage.getMemoryUsage() : "")); 448 } 449 for (Iterator<MessageReference> iterator = memoryList.iterator(); iterator.hasNext();) { 450 MessageReference node = iterator.next(); 451 node.decrementReferenceCount(); 452 ByteSequence bs; 453 try { 454 bs = getByteSequence(node.getMessage()); 455 getDiskList().addLast(node.getMessageId().toString(), bs); 456 } catch (IOException e) { 457 LOG.error("Failed to write to disk list", e); 458 throw new RuntimeException(e); 459 } 460 461 } 462 memoryList.clear(); 463 setCacheEnabled(false); 464 LOG.trace("{}, flushToDisk() done - {} ms {}", 465 name, 466 (System.currentTimeMillis() - start), 467 (systemUsage != null ? systemUsage.getMemoryUsage() : "")); 468 } 469 } 470 471 protected boolean isDiskListEmpty() { 472 return diskList == null || diskList.isEmpty(); 473 } 474 475 public PList getDiskList() { 476 if (diskList == null) { 477 try { 478 diskList = store.getPList(name); 479 } catch (Exception e) { 480 LOG.error("Caught an IO Exception getting the DiskList {}", name, e); 481 throw new RuntimeException(e); 482 } 483 } 484 return diskList; 485 } 486 487 private void discardExpiredMessage(MessageReference reference) { 488 LOG.debug("Discarding expired message {}", reference); 489 if (reference.isExpired() && broker.isExpired(reference)) { 490 ConnectionContext context = new ConnectionContext(); 491 context.setBroker(broker); 492 ((Destination)reference.getRegionDestination()).messageExpired(context, null, new IndirectMessageReference(reference.getMessage())); 493 } 494 } 495 496 protected ByteSequence getByteSequence(Message message) throws IOException { 497 org.apache.activemq.util.ByteSequence packet = wireFormat.marshal(message); 498 return new ByteSequence(packet.data, packet.offset, packet.length); 499 } 500 501 protected Message getMessage(ByteSequence bs) throws IOException { 502 org.apache.activemq.util.ByteSequence packet = new org.apache.activemq.util.ByteSequence(bs.getData(), bs 503 .getOffset(), bs.getLength()); 504 return (Message) this.wireFormat.unmarshal(packet); 505 506 } 507 508 final class DiskIterator implements Iterator<MessageReference> { 509 private final PList.PListIterator iterator; 510 DiskIterator() { 511 try { 512 iterator = getDiskList().iterator(); 513 } catch (Exception e) { 514 throw new RuntimeException(e); 515 } 516 } 517 518 @Override 519 public boolean hasNext() { 520 return iterator.hasNext(); 521 } 522 523 @Override 524 public MessageReference next() { 525 try { 526 PListEntry entry = iterator.next(); 527 Message message = getMessage(entry.getByteSequence()); 528 message.getMessageId().setPlistLocator(entry.getLocator()); 529 return message; 530 } catch (IOException e) { 531 LOG.error("I/O error", e); 532 throw new RuntimeException(e); 533 } 534 } 535 536 @Override 537 public void remove() { 538 iterator.remove(); 539 } 540 541 public void release() { 542 iterator.release(); 543 } 544 } 545}