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