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}