/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hazelcast.collection.impl.queue; import com.hazelcast.collection.impl.txnqueue.TxQueueItem; import com.hazelcast.config.QueueConfig; import com.hazelcast.config.QueueStoreConfig; import com.hazelcast.core.HazelcastException; import com.hazelcast.logging.ILogger; import com.hazelcast.monitor.impl.LocalQueueStatsImpl; import com.hazelcast.nio.ObjectDataInput; import com.hazelcast.nio.ObjectDataOutput; import com.hazelcast.nio.serialization.Data; import com.hazelcast.nio.serialization.IdentifiedDataSerializable; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.serialization.SerializationService; import com.hazelcast.transaction.TransactionException; import com.hazelcast.util.Clock; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * This class contains methods be notable for the Queue. * such as pool,peek,clear.. */ /** * The {@code QueueContainer} contains the actual queue and provides functionalities such as : * <ul> * <li>queue functionalities</li> * <li>transactional operation functionalities</li> * <li>schedules queue destruction if it is configured to be destroyed once empty</li> * </ul> */ @SuppressWarnings("checkstyle:methodcount") public class QueueContainer implements IdentifiedDataSerializable { private static final int ID_PROMOTION_OFFSET = 100000; /** Contains item ID to queue item mappings for current transactions */ private final Map<Long, TxQueueItem> txMap = new HashMap<Long, TxQueueItem>(); private final Map<Long, Data> dataMap = new HashMap<Long, Data>(); private QueueWaitNotifyKey pollWaitNotifyKey; private QueueWaitNotifyKey offerWaitNotifyKey; private LinkedList<QueueItem> itemQueue; private Map<Long, QueueItem> backupMap; private QueueConfig config; private QueueStoreWrapper store; private NodeEngine nodeEngine; private QueueService service; private ILogger logger; /** The ID of the last item, used for generating unique IDs for queue items */ private long idGenerator; private String name; private long minAge = Long.MAX_VALUE; private long maxAge = Long.MIN_VALUE; private long totalAge; private long totalAgedCount; private boolean isEvictionScheduled; /** * The default no-args constructor is only meant for factory usage. */ public QueueContainer() { } public QueueContainer(String name) { this.name = name; pollWaitNotifyKey = new QueueWaitNotifyKey(name, "poll"); offerWaitNotifyKey = new QueueWaitNotifyKey(name, "offer"); } public QueueContainer(String name, QueueConfig config, NodeEngine nodeEngine, QueueService service) throws Exception { this(name); setConfig(config, nodeEngine, service); } /** * Initializes the item queue with items from the queue store if the store is enabled and if item queue is not being * initialized as a part of a backup operation. If the item queue is being initialized as a part of a backup operation then * the operation is in charge of adding items to a queue and the items are not loaded from a queue store. * * @param fromBackup indicates if this item queue is being initialized from a backup operation. If false, the * item queue will initialize from the queue store. If true, it will not initialize */ public void init(boolean fromBackup) { if (!fromBackup && store.isEnabled()) { Set<Long> keys = store.loadAllKeys(); if (keys != null) { long maxId = -1; for (Long key : keys) { QueueItem item = new QueueItem(this, key, null); getItemQueue().offer(item); maxId = Math.max(maxId, key); } idGenerator = maxId + 1; } } } public QueueStoreWrapper getStore() { return store; } //TX Methods /** * Checks if there is a reserved item (within a transaction) with the given {@code itemId}. * * @param itemId the ID which is to be checked * @return true if there is an item reserved with this ID * @throws TransactionException if there is no reserved item with the ID */ public boolean txnCheckReserve(long itemId) { if (txMap.get(itemId) == null) { throw new TransactionException("No reserve for itemId: " + itemId); } return true; } public void txnEnsureBackupReserve(long itemId, String transactionId, boolean pollOperation) { if (txMap.get(itemId) == null) { if (pollOperation) { txnPollBackupReserve(itemId, transactionId); } else { txnOfferBackupReserve(itemId, transactionId); } } } //TX Poll /** * Retrieves and removes the head of the queue and loads the data from the queue store if the data is not stored in-memory * and the queue store is configured and enabled. If the queue is empty returns an item which was previously reserved with * the {@code reservedOfferId} by invoking {@code {@link #txnOfferReserve(String)}}. * * @param reservedOfferId the ID of the reserved item to be returned if the queue is empty * @param transactionId the transaction ID for which this poll is invoked * @return the head of the queue or a reserved item with the {@code reservedOfferId} if there is any */ public QueueItem txnPollReserve(long reservedOfferId, String transactionId) { QueueItem item = getItemQueue().peek(); if (item == null) { TxQueueItem txItem = txMap.remove(reservedOfferId); if (txItem == null) { return null; } item = new QueueItem(this, txItem.getItemId(), txItem.getData()); return item; } if (store.isEnabled() && item.getData() == null) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } getItemQueue().poll(); txMap.put(item.getItemId(), new TxQueueItem(item).setPollOperation(true).setTransactionId(transactionId)); return item; } public void txnPollBackupReserve(long itemId, String transactionId) { QueueItem item = getBackupMap().remove(itemId); if (item == null) { logger.warning("Backup reserve failed, itemId: " + itemId + " is not found"); return; } txMap.put(itemId, new TxQueueItem(item).setPollOperation(true).setTransactionId(transactionId)); } public Data txnCommitPoll(long itemId) { final Data result = txnCommitPollBackup(itemId); scheduleEvictionIfEmpty(); return result; } /** * Commits the effects of the {@link #txnPollReserve(long, String)}}. Also deletes the item data from the queue * data store if it is configured and enabled. * * @param itemId the ID of the item which was polled inside a transaction * @return the data of the polled item * @throws HazelcastException if there was any exception while removing the item from the queue data store */ public Data txnCommitPollBackup(long itemId) { TxQueueItem item = txMap.remove(itemId); if (item == null) { logger.warning("txnCommitPoll operation-> No txn item for itemId: " + itemId); return null; } if (store.isEnabled()) { try { store.delete(item.getItemId()); } catch (Exception e) { logger.severe("Error during store delete: " + item.getItemId(), e); } } return item.getData(); } /** * Rolls back the effects of the {@link #txnPollReserve(long, String)}. The {@code backup} parameter defines whether * this item was stored on a backup queue or a primary queue. Also adds a queue item with the {@code itemId} to the backup * map if the {@code backup} parameter is true or the queue. * Cancels the queue eviction if one is scheduled. * * @param itemId the ID of the item which was polled in a transaction * @param backup if this item was * @return if there was any polled item with the {@code itemId} inside a transaction */ public boolean txnRollbackPoll(long itemId, boolean backup) { TxQueueItem item = txMap.remove(itemId); if (item == null) { return false; } if (backup) { getBackupMap().put(itemId, item); } else { addTxItemOrdered(item); } cancelEvictionIfExists(); return true; } @SuppressWarnings("unchecked") private void addTxItemOrdered(TxQueueItem txQueueItem) { final ListIterator<QueueItem> iterator = ((List<QueueItem>) getItemQueue()).listIterator(); while (iterator.hasNext()) { final QueueItem queueItem = iterator.next(); if (txQueueItem.itemId < queueItem.itemId) { iterator.previous(); break; } } iterator.add(txQueueItem); } //TX Offer /** * Reserves an ID for a future queue item and associates it with the given {@code transactionId}. * The item is not yet visible in the queue, it is just reserved for future insertion. * * @param transactionId the ID of the transaction offering this item * @return the ID of the reserved item */ public long txnOfferReserve(String transactionId) { TxQueueItem item = new TxQueueItem(this, nextId(), null).setTransactionId(transactionId).setPollOperation(false); txMap.put(item.getItemId(), item); return item.getItemId(); } /** * Reserves an ID for a future queue item and associates it with the given {@code transactionId}. * The item is not yet visible in the queue, it is just reserved for future insertion. * * @param transactionId the ID of the transaction offering this item * @param itemId the ID of the item being reserved */ public void txnOfferBackupReserve(long itemId, String transactionId) { QueueItem item = new QueueItem(this, itemId, null); Object o = txMap.put(itemId, new TxQueueItem(item).setPollOperation(false).setTransactionId(transactionId)); if (o != null) { logger.severe("txnOfferBackupReserve operation-> Item exists already at txMap for itemId: " + itemId); } } /** * Sets the data of a reserved item and commits the change so it can be visible outside a transaction. The commit means * that the item is offered to the queue if {@code backup} is false or saved into a backup map if {@code backup} is true. * This is because a node can hold backups for queues on other nodes. * Cancels the queue eviction if one is scheduled. * * @param itemId the ID of the reserved item * @param data the data to be associated with the reserved item * @param backup if the item is to be offered to the underlying queue or stored as a backup * @return true if the commit succeeded * @throws TransactionException if there is no reserved item with the {@code itemId} */ public boolean txnCommitOffer(long itemId, Data data, boolean backup) { QueueItem item = txMap.remove(itemId); if (item == null && !backup) { throw new TransactionException("No reserve: " + itemId); } else if (item == null) { item = new QueueItem(this, itemId, data); } item.setData(data); if (!backup) { getItemQueue().offer(item); cancelEvictionIfExists(); } else { getBackupMap().put(itemId, item); } if (store.isEnabled() && !backup) { try { store.store(item.getItemId(), data); } catch (Exception e) { logger.warning("Exception during store", e); } } return true; } /** * Removes a reserved item with the given {@code itemId}. Also schedules the queue for destruction if it is empty or * destroys it immediately if it is empty and {@link QueueConfig#getEmptyQueueTtl()} is 0. * * @param itemId the ID of the reserved item to be removed * @return if an item was reserved with the given {@code itemId} */ public boolean txnRollbackOffer(long itemId) { final boolean result = txnRollbackOfferBackup(itemId); scheduleEvictionIfEmpty(); return result; } /** * Removes a reserved item with the given {@code itemId}. * * @param itemId the ID of the reserved item to be removed * @return if an item was reserved with the given {@code itemId} */ public boolean txnRollbackOfferBackup(long itemId) { QueueItem item = txMap.remove(itemId); if (item == null) { logger.warning("txnRollbackOffer operation-> No txn item for itemId: " + itemId); return false; } return true; } /** * Retrieves, but does not remove, the head of the queue. If the queue is empty checks if there is a reserved item with * the associated {@code offerId} and returns it. * If the item was retrieved from the queue but does not contain any data and the queue store is enabled, this method will * try load the data from the data store. * * @param offerId the ID of the reserved item to be returned if the queue is empty * @param transactionId currently ignored * @return the head of the queue or a reserved item associated with the {@code offerId} if the queue is empty * @throws HazelcastException if there is an exception while loading the data from the queue store */ public QueueItem txnPeek(long offerId, String transactionId) { QueueItem item = getItemQueue().peek(); if (item == null) { if (offerId == -1) { return null; } TxQueueItem txItem = txMap.get(offerId); if (txItem == null) { return null; } item = new QueueItem(this, txItem.getItemId(), txItem.getData()); return item; } if (store.isEnabled() && item.getData() == null) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } return item; } //TX Methods Ends public long offer(Data data) { QueueItem item = new QueueItem(this, nextId(), null); if (store.isEnabled()) { try { store.store(item.getItemId(), data); } catch (Exception e) { throw new HazelcastException(e); } } if (!store.isEnabled() || store.getMemoryLimit() > getItemQueue().size()) { item.setData(data); } getItemQueue().offer(item); cancelEvictionIfExists(); return item.getItemId(); } public void offerBackup(Data data, long itemId) { QueueItem item = new QueueItem(this, itemId, null); if (!store.isEnabled() || store.getMemoryLimit() > getItemQueue().size()) { item.setData(data); } getBackupMap().put(itemId, item); } /** * Adds all items from the {@code dataList} to the queue. The data will be stored in the queue store if configured and * enabled. If the store is enabled, only {@link QueueStoreWrapper#getMemoryLimit()} item data will be stored in memory. * Cancels the eviction if one is scheduled. * * @param dataList the items to be added to the queue and stored in the queue store * @return map of item ID and items added */ public Map<Long, Data> addAll(Collection<Data> dataList) { final Map<Long, Data> map = new HashMap<Long, Data>(dataList.size()); final List<QueueItem> list = new ArrayList<QueueItem>(dataList.size()); for (Data data : dataList) { final QueueItem item = new QueueItem(this, nextId(), null); if (!store.isEnabled() || store.getMemoryLimit() > getItemQueue().size()) { item.setData(data); } map.put(item.getItemId(), data); list.add(item); } if (store.isEnabled() && !map.isEmpty()) { try { store.storeAll(map); } catch (Exception e) { throw new HazelcastException(e); } } if (!list.isEmpty()) { getItemQueue().addAll(list); cancelEvictionIfExists(); } return map; } public void addAllBackup(Map<Long, Data> dataMap) { for (Map.Entry<Long, Data> entry : dataMap.entrySet()) { QueueItem item = new QueueItem(this, entry.getKey(), null); if (!store.isEnabled() || store.getMemoryLimit() > getItemQueue().size()) { item.setData(entry.getValue()); } getBackupMap().put(item.getItemId(), item); } } /** * Retrieves, but does not remove, the head of this queue, or returns {@code null} if this queue is empty. * Loads the data from the queue store if the item data is empty. * * @return the first item in the queue */ public QueueItem peek() { final QueueItem item = getItemQueue().peek(); if (item == null) { return null; } if (store.isEnabled() && item.getData() == null) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } return item; } /** * Retrieves and removes the head of the queue (in other words, the first item), or returns {@code null} if it is empty. * Also calls the queue store for item deletion by item ID. * * @return the first item in the queue */ public QueueItem poll() { final QueueItem item = peek(); if (item == null) { return null; } if (store.isEnabled()) { try { store.delete(item.getItemId()); } catch (Exception e) { throw new HazelcastException(e); } } getItemQueue().poll(); age(item, Clock.currentTimeMillis()); scheduleEvictionIfEmpty(); return item; } public void pollBackup(long itemId) { QueueItem item = getBackupMap().remove(itemId); if (item != null) { //For Stats age(item, Clock.currentTimeMillis()); } } /** * Removes items from the queue and the queue store (if configured), up to {@code maxSize} or the size of the queue, * whichever is smaller. Also schedules the queue for destruction if it is empty or destroys it immediately if it is * empty and {@link QueueConfig#getEmptyQueueTtl()} is 0. * * @param maxSize the maximum number of items to be removed * @return the map of IDs and removed (drained) items */ public Map<Long, Data> drain(int maxSize) { int maxSizeParam = maxSize; if (maxSizeParam < 0 || maxSizeParam > getItemQueue().size()) { maxSizeParam = getItemQueue().size(); } final LinkedHashMap<Long, Data> map = new LinkedHashMap<Long, Data>(maxSizeParam); mapDrainIterator(maxSizeParam, map); if (store.isEnabled() && maxSizeParam != 0) { try { store.deleteAll(map.keySet()); } catch (Exception e) { throw new HazelcastException(e); } } long current = Clock.currentTimeMillis(); for (int i = 0; i < maxSizeParam; i++) { final QueueItem item = getItemQueue().poll(); //For Stats age(item, current); } if (maxSizeParam != 0) { scheduleEvictionIfEmpty(); } return map; } public void mapDrainIterator(int maxSize, Map map) { Iterator<QueueItem> iter = getItemQueue().iterator(); for (int i = 0; i < maxSize; i++) { QueueItem item = iter.next(); if (store.isEnabled() && item.getData() == null) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } map.put(item.getItemId(), item.getData()); } } public void drainFromBackup(Set<Long> itemIdSet) { for (Long itemId : itemIdSet) { pollBackup(itemId); } dataMap.clear(); } public int size() { return Math.min(config.getMaxSize(), getItemQueue().size()); } public int txMapSize() { return txMap.size(); } public int backupSize() { return getBackupMap().size(); } public Map<Long, Data> clear() { long current = Clock.currentTimeMillis(); LinkedHashMap<Long, Data> map = new LinkedHashMap<Long, Data>(getItemQueue().size()); for (QueueItem item : getItemQueue()) { map.put(item.getItemId(), item.getData()); // For stats age(item, current); } if (store.isEnabled() && !map.isEmpty()) { try { store.deleteAll(map.keySet()); } catch (Exception e) { throw new HazelcastException(e); } } getItemQueue().clear(); dataMap.clear(); scheduleEvictionIfEmpty(); return map; } public void clearBackup(Set<Long> itemIdSet) { drainFromBackup(itemIdSet); } /** * iterates all items, checks equality with data * This method does not trigger store load. */ public long remove(Data data) { Iterator<QueueItem> iter = getItemQueue().iterator(); while (iter.hasNext()) { QueueItem item = iter.next(); if (data.equals(item.getData())) { if (store.isEnabled()) { try { store.delete(item.getItemId()); } catch (Exception e) { throw new HazelcastException(e); } } iter.remove(); //For Stats age(item, Clock.currentTimeMillis()); scheduleEvictionIfEmpty(); return item.getItemId(); } } return -1; } public void removeBackup(long itemId) { getBackupMap().remove(itemId); } /** * Checks if the queue contains all items in the dataSet. This method does not trigger store load. * * @param dataSet the items which should be stored in the queue * @return true if the queue contains all items, false otherwise */ public boolean contains(Collection<Data> dataSet) { for (Data data : dataSet) { boolean contains = false; for (QueueItem item : getItemQueue()) { if (item.getData() != null && item.getData().equals(data)) { contains = true; break; } } if (!contains) { return false; } } return true; } /** * Returns data in the queue. This method triggers store load. * * @return the item data in the queue. */ public List<Data> getAsDataList() { final List<Data> dataList = new ArrayList<Data>(getItemQueue().size()); for (QueueItem item : getItemQueue()) { if (store.isEnabled() && item.getData() == null) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } dataList.add(item.getData()); } return dataList; } /** * Compares if the queue contains the items in the dataList and removes them according to the retain parameter. If * the retain parameter is true, it will remove items which are not in the dataList (retaining the items which are in the * list). If the retain parameter is false, it will remove items which are in the dataList (retaining all other items which * are not in the list). * * Note: this method will trigger store load. * * @param dataList the list of items which are to be retained in the queue or which are to be removed from the queue * @param retain does the method retain the items in the list (true) or remove them from the queue (false) * @return map of removed items by id */ public Map<Long, Data> compareAndRemove(Collection<Data> dataList, boolean retain) { final LinkedHashMap<Long, Data> map = new LinkedHashMap<Long, Data>(); for (QueueItem item : getItemQueue()) { if (item.getData() == null && store.isEnabled()) { try { load(item); } catch (Exception e) { throw new HazelcastException(e); } } boolean contains = dataList.contains(item.getData()); if ((retain && !contains) || (!retain && contains)) { map.put(item.getItemId(), item.getData()); } } mapIterateAndRemove(map); return map; } /** * Deletes items from the queue which have IDs contained in the key set of the given map. Also schedules the queue for * destruction if it is empty or destroys it immediately if it is empty and {@link QueueConfig#getEmptyQueueTtl()} is 0. * * @param map the map of items which to be removed. */ public void mapIterateAndRemove(Map<Long, Data> map) { if (map.size() <= 0) { return; } if (store.isEnabled()) { try { store.deleteAll(map.keySet()); } catch (Exception e) { throw new HazelcastException(e); } } final Iterator<QueueItem> iter = getItemQueue().iterator(); while (iter.hasNext()) { final QueueItem item = iter.next(); if (map.containsKey(item.getItemId())) { iter.remove(); //For Stats age(item, Clock.currentTimeMillis()); } } scheduleEvictionIfEmpty(); } public void compareAndRemoveBackup(Set<Long> itemIdSet) { drainFromBackup(itemIdSet); } /** * Tries to load the data for the given queue item. The method will also try to load data in batches if configured to do so. * <p> * If the {@link QueueStoreWrapper#getBulkLoad()} is 1, it will just load data for the given item. Otherwise, it will * load items for other items in the queue too by collecting the IDs of the items in the queue sequentially up to * {@link QueueStoreWrapper#getBulkLoad()} items. While doing so, it will make sure that the ID of the initially requested * item is also being loaded even though it is not amongst the first {@link QueueStoreWrapper#getBulkLoad()} items in the * queue. * * @param item the item for which the data is being set * @throws Exception if there is any exception. For example, when calling methods on the queue store */ private void load(QueueItem item) throws Exception { int bulkLoad = store.getBulkLoad(); bulkLoad = Math.min(getItemQueue().size(), bulkLoad); if (bulkLoad == 1) { item.setData(store.load(item.getItemId())); } else if (bulkLoad > 1) { final Iterator<QueueItem> iter = getItemQueue().iterator(); final HashSet<Long> keySet = new HashSet<Long>(bulkLoad); keySet.add(item.getItemId()); while (keySet.size() < bulkLoad) { keySet.add(iter.next().getItemId()); } final Map<Long, Data> values = store.loadAll(keySet); dataMap.putAll(values); item.setData(getDataFromMap(item.getItemId())); } } /** * Returns if this queue can accommodate one item. * * @return if the queue has capacity for one item */ public boolean hasEnoughCapacity() { return hasEnoughCapacity(1); } /** * Returns if this queue can accommodate for {@code delta} items. * * @param delta the number of items that should be stored in the queue * @return if the queue has enough capacity for the items */ public boolean hasEnoughCapacity(int delta) { return (getItemQueue().size() + delta) <= config.getMaxSize(); } public Deque<QueueItem> getItemQueue() { if (itemQueue == null) { itemQueue = new LinkedList<QueueItem>(); if (backupMap != null && !backupMap.isEmpty()) { List<QueueItem> values = new ArrayList<QueueItem>(backupMap.values()); Collections.sort(values); itemQueue.addAll(values); final QueueItem lastItem = itemQueue.peekLast(); if (lastItem != null) { setId(lastItem.itemId + ID_PROMOTION_OFFSET); } backupMap.clear(); backupMap = null; } } return itemQueue; } Map<Long, QueueItem> getBackupMap() { if (backupMap == null) { backupMap = new HashMap<Long, QueueItem>(); if (itemQueue != null) { for (QueueItem item : itemQueue) { backupMap.put(item.getItemId(), item); } itemQueue.clear(); itemQueue = null; } } return backupMap; } public Data getDataFromMap(long itemId) { return dataMap.remove(itemId); } public void setConfig(QueueConfig config, NodeEngine nodeEngine, QueueService service) { this.nodeEngine = nodeEngine; this.service = service; this.logger = nodeEngine.getLogger(QueueContainer.class); this.config = new QueueConfig(config); // init queue store. final QueueStoreConfig storeConfig = config.getQueueStoreConfig(); final SerializationService serializationService = nodeEngine.getSerializationService(); ClassLoader classLoader = nodeEngine.getConfigClassLoader(); this.store = QueueStoreWrapper.create(name, storeConfig, serializationService, classLoader); } long nextId() { return ++idGenerator; } public QueueWaitNotifyKey getPollWaitNotifyKey() { return pollWaitNotifyKey; } public QueueWaitNotifyKey getOfferWaitNotifyKey() { return offerWaitNotifyKey; } public QueueConfig getConfig() { return config; } private void age(QueueItem item, long currentTime) { long elapsed = currentTime - item.getCreationTime(); if (elapsed <= 0) { //elapsed time can not be a negative value, a system clock problem maybe. ignored return; } totalAgedCount++; totalAge += elapsed; minAge = Math.min(minAge, elapsed); maxAge = Math.max(maxAge, elapsed); } public void setStats(LocalQueueStatsImpl stats) { stats.setMinAge(minAge); stats.setMaxAge(maxAge); long totalAgedCountVal = Math.max(totalAgedCount, 1); stats.setAveAge(totalAge / totalAgedCountVal); } /** * Schedules the queue for destruction if the queue is empty. Destroys the queue immediately the queue is empty and the * {@link QueueConfig#getEmptyQueueTtl()} is 0. Upon scheduled execution, the queue will be checked if it is still empty. */ private void scheduleEvictionIfEmpty() { final int emptyQueueTtl = config.getEmptyQueueTtl(); if (emptyQueueTtl < 0) { return; } if (getItemQueue().isEmpty() && txMap.isEmpty() && !isEvictionScheduled) { if (emptyQueueTtl == 0) { nodeEngine.getProxyService().destroyDistributedObject(QueueService.SERVICE_NAME, name); } else if (emptyQueueTtl > 0) { service.scheduleEviction(name, TimeUnit.SECONDS.toMillis(emptyQueueTtl)); isEvictionScheduled = true; } } } public void cancelEvictionIfExists() { if (isEvictionScheduled) { service.cancelEviction(name); isEvictionScheduled = false; } } public boolean isEvictable() { return getItemQueue().isEmpty() && txMap.isEmpty(); } public void rollbackTransaction(String transactionId) { final Iterator<TxQueueItem> iterator = txMap.values().iterator(); while (iterator.hasNext()) { final TxQueueItem item = iterator.next(); if (transactionId.equals(item.getTransactionId())) { iterator.remove(); if (item.isPollOperation()) { getItemQueue().offerFirst(item); cancelEvictionIfExists(); } } } } @Override public void writeData(ObjectDataOutput out) throws IOException { out.writeUTF(name); out.writeInt(getItemQueue().size()); for (QueueItem item : getItemQueue()) { out.writeObject(item); } out.writeInt(txMap.size()); for (TxQueueItem item : txMap.values()) { item.writeData(out); } } @Override public void readData(ObjectDataInput in) throws IOException { name = in.readUTF(); pollWaitNotifyKey = new QueueWaitNotifyKey(name, "poll"); offerWaitNotifyKey = new QueueWaitNotifyKey(name, "offer"); int size = in.readInt(); for (int j = 0; j < size; j++) { QueueItem item = in.readObject(); getItemQueue().offer(item); setId(item.getItemId()); } int txSize = in.readInt(); for (int j = 0; j < txSize; j++) { TxQueueItem item = new TxQueueItem(this, -1, null); item.readData(in); txMap.put(item.getItemId(), item); setId(item.getItemId()); } } public void destroy() { if (itemQueue != null) { itemQueue.clear(); } if (backupMap != null) { backupMap.clear(); } txMap.clear(); dataMap.clear(); } @Override public int getFactoryId() { return QueueDataSerializerHook.F_ID; } @Override public int getId() { return QueueDataSerializerHook.QUEUE_CONTAINER; } void setId(long itemId) { idGenerator = Math.max(itemId + 1, idGenerator); } }