Package com.hazelcast.map

Source Code of com.hazelcast.map.AbstractEvictableRecordStore

package com.hazelcast.map;

import com.hazelcast.config.MapConfig;
import com.hazelcast.map.eviction.EvictionHelper;
import com.hazelcast.map.record.Record;
import com.hazelcast.nio.serialization.Data;

import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

import static com.hazelcast.map.eviction.EvictionHelper.fireEvent;
import static com.hazelcast.map.eviction.EvictionHelper.removeEvictableRecords;

/**
* Contains eviction specific functionality.
*/
abstract class AbstractEvictableRecordStore extends AbstractRecordStore {

    /**
     * Number of reads before clean up.
     * A nice number such as 2^n - 1.
     */
    protected static final int POST_READ_CHECK_POINT = 63;

    /**
     * Flag for checking if this record store has at least one candidate entry
     * for expiration (idle or tll) or not.
     */
    protected volatile boolean expirable;

    /**
     * Iterates over a pre-set entry count/percentage in one round.
     * Used in expiration logic for traversing entries. Initializes lazily.
     */
    protected Iterator<Record> expirationIterator;

    /**
     * If there is no clean-up caused by puts after some time,
     * count a number of gets and start eviction.
     */
    protected int readCountBeforeCleanUp;

    /**
     * used in LRU eviction logic.
     */
    protected long lruAccessSequenceNumber;

    protected boolean evictionEnabled;


    /**
     * Last run time of cleanup operation.
     */
    protected long lastEvictionTime;

    protected AbstractEvictableRecordStore(MapContainer mapContainer, int partitionId) {
        super(mapContainer, partitionId);
        this.evictionEnabled
                = !MapConfig.EvictionPolicy.NONE.equals(mapContainer.getMapConfig().getEvictionPolicy());
        this.expirable = isRecordStoreExpirable();
    }

    private boolean isRecordStoreExpirable() {
        return mapContainer.getMapConfig().getMaxIdleSeconds() > 0
                || mapContainer.getMapConfig().getTimeToLiveSeconds() > 0;
    }

    @Override
    public void evictExpiredEntries(int percentage, boolean ownerPartition) {
        final long now = getNow();
        final int size = size();
        final int maxIterationCount = getMaxIterationCount(size, percentage);
        final int maxRetry = 3;
        int loop = 0;
        int evictedEntryCount = 0;
        while (true) {
            evictedEntryCount += evictExpiredEntries0(maxIterationCount, now, ownerPartition);
            if (evictedEntryCount >= maxIterationCount) {
                break;
            }
            loop++;
            if (loop > maxRetry) {
                break;
            }
        }
    }

    @Override
    public boolean isExpirable() {
        return expirable;
    }

    /**
     * Intended to put an upper bound to iterations. Used in evictions.
     *
     * @param size       of iterate-able.
     * @param percentage percentage of size.
     * @return 100 If calculated iteration count is less than 100, otherwise returns calculated iteration count.
     */
    private int getMaxIterationCount(int size, int percentage) {
        final int defaultMaxIterationCount = 100;
        final float oneHundred = 100F;
        float maxIterationCount = size * (percentage / oneHundred);
        if (maxIterationCount <= defaultMaxIterationCount) {
            return defaultMaxIterationCount;
        }
        return Math.round(maxIterationCount);
    }

    private int evictExpiredEntries0(int maxIterationCount, long now, boolean ownerPartition) {
        int evictedCount = 0;
        int checkedEntryCount = 0;
        initExpirationIterator();
        while (expirationIterator.hasNext()) {
            if (checkedEntryCount >= maxIterationCount) {
                break;
            }
            checkedEntryCount++;
            final Record record = expirationIterator.next();
            final Data key = record.getKey();
            if (isLocked(key)) {
                continue;
            }
            if (isReachable(record, now)) {
                continue;
            }
            //!!! get entry value here because evictInternal(key) nulls the record value.
            final Object value = record.getValue();
            evictInternal(key);
            evictedCount++;
            initExpirationIterator();

            // do post eviction operations if this partition is an owner partition.
            if (ownerPartition) {
                doPostEvictionOperations(key, value);
            }
            if (!expirationIterator.hasNext()) {
                break;
            }
        }
        return evictedCount;
    }

    private void initExpirationIterator() {
        if (expirationIterator == null || !expirationIterator.hasNext()) {
            expirationIterator = records.values().iterator();
        }
    }

    protected void resetAccessSequenceNumber() {
        lruAccessSequenceNumber = 0L;
    }

    /**
     * TODO make checkEvictable fast by carrying threshold logic to partition.
     * This cleanup adds some latency to write operations.
     * But it sweeps records much better under high write loads.
     * <p/>
     *
     * @param now now in time.
     */
    protected void earlyWriteCleanup(long now) {
        if (evictionEnabled) {
            cleanUp(now);
        }
    }

    /**
     * If there is no clean-up caused by puts after some time,
     * try to clean-up from gets.
     *
     * @param now now.
     */
    protected void postReadCleanUp(long now) {
        if (evictionEnabled) {
            readCountBeforeCleanUp++;
            if ((readCountBeforeCleanUp & POST_READ_CHECK_POINT) == 0) {
                cleanUp(now);
            }
        }

    }

    private void cleanUp(long now) {
        if (size() == 0) {
            return;
        }
        if (inEvictableTimeWindow(now) && isEvictable()) {
            removeEvictables();
            lastEvictionTime = now;
            readCountBeforeCleanUp = 0;
        }
    }

    private void removeEvictables() {
        final int evictableSize = getEvictableSize();
        if (evictableSize < 1) {
            return;
        }
        final MapConfig mapConfig = mapContainer.getMapConfig();
        removeEvictableRecords(this, evictableSize, mapConfig, mapServiceContext);
    }

    private int getEvictableSize() {
        final int size = size();
        if (size < 1) {
            return 0;
        }
        final int evictableSize
                = EvictionHelper.getEvictableSize(size, mapContainer.getMapConfig(), mapServiceContext);
        if (evictableSize < 1) {
            return 0;
        }
        return evictableSize;
    }


    /**
     * Eviction waits at least 1000 milliseconds to run.
     *
     * @return <code>true</code> if in that time window,
     * otherwise <code>false</code>
     */
    private boolean inEvictableTimeWindow(long now) {
        final int evictAfterMs = 1000;
        return (now - lastEvictionTime) > evictAfterMs;
    }

    private boolean isEvictable() {
        return EvictionHelper.checkEvictable(mapContainer);
    }

    protected Record nullIfExpired(Record record) {
        return evictIfNotReachable(record);
    }

    protected void markRecordStoreExpirable(long ttl) {
        if (ttl > 0L) {
            expirable = true;
        }
    }

    abstract Object evictInternal(Data key);


    /**
     * Check if record is reachable according to ttl or idle times.
     * If not reachable return null.
     *
     * @param record {@link com.hazelcast.map.record.Record}
     * @return null if evictable.
     */
    private Record evictIfNotReachable(Record record) {
        if (record == null) {
            return null;
        }
        final Data key = record.getKey();
        if (isLocked(key)) {
            return record;
        }
        if (isReachable(record)) {
            return record;
        }
        final Object value = record.getValue();
        evict(key);
        doPostEvictionOperations(key, value);
        return null;
    }

    private boolean isReachable(Record record) {
        final long now = getNow();
        return isReachable(record, now);
    }

    private boolean isReachable(Record record, long time) {
        if (record == null) {
            return false;
        }
        final Record idleExpired = isIdleExpired(record, time);
        if (idleExpired == null) {
            return false;
        }
        final Record ttlExpired = isTTLExpired(record, time);

        return ttlExpired != null;
    }

    private Record isIdleExpired(Record record, long time) {
        if (record == null) {
            return null;
        }
        boolean result;
        // lastAccessTime : updates on every touch (put/get).
        final long lastAccessTime = record.getLastAccessTime();

        assert lastAccessTime > 0L;
        assert time > 0L;
        assert time >= lastAccessTime;

        final long idleTime = getIdleTime();
        result = time - lastAccessTime >= idleTime;

        return result ? null : record;
    }

    private long getIdleTime() {
        final int maxIdleSeconds = mapContainer.getMapConfig().getMaxIdleSeconds();
        return maxIdleSeconds == 0 ? Long.MAX_VALUE : mapServiceContext.convertTime(maxIdleSeconds, TimeUnit.SECONDS);
    }

    private Record isTTLExpired(Record record, long time) {
        if (record == null) {
            return null;
        }
        boolean result;
        final long ttl = record.getTtl();
        // when ttl is zero or negative, it should remain eternally.
        if (ttl < 1L) {
            return record;
        }
        final long creationTime = record.getCreationTime();

        assert ttl > 0L : String.format("wrong ttl %d", ttl);
        assert creationTime > 0L : String.format("wrong creationTime %d", creationTime);
        assert time > 0L : String.format("wrong time %d", time);
        assert time >= creationTime : String.format("time >= lastUpdateTime (%d >= %d)",
                time, creationTime);

        result = time - creationTime >= ttl;
        return result ? null : record;
    }

    /**
     * - Sends eviction event.
     * - Invalidates near cache.
     *
     * @param key   the key to be processed.
     * @param value the value to be processed.
     */
    private void doPostEvictionOperations(Data key, Object value) {
        final NearCacheProvider nearCacheProvider = mapServiceContext.getNearCacheProvider();
        if (nearCacheProvider.isNearCacheAndInvalidationEnabled(name)) {
            nearCacheProvider.invalidateAllNearCaches(name, key);
        }
        fireEvent(key, value, name, mapServiceContext);
    }

    protected void increaseRecordEvictionCriteriaNumber(Record record, MapConfig.EvictionPolicy evictionPolicy) {
        switch (evictionPolicy) {
            case LRU:
                ++lruAccessSequenceNumber;
                record.setEvictionCriteriaNumber(lruAccessSequenceNumber);
                break;
            case LFU:
                record.setEvictionCriteriaNumber(record.getEvictionCriteriaNumber() + 1L);
                break;
            case NONE:
                break;
            default:
                throw new IllegalArgumentException("Not an appropriate eviction policy [" + evictionPolicy + ']');
        }
    }

    @Override
    protected void accessRecord(Record record, long now) {
        super.accessRecord(record, now);
        increaseRecordEvictionCriteriaNumber(record, mapContainer.getMapConfig().getEvictionPolicy());
    }


    /**
     * Read only iterator. Iterates by checking whether a record expired or not.
     */
    protected final class ReadOnlyRecordIterator implements Iterator<Record> {

        private final Iterator<Record> iterator;
        private Record nextRecord;
        private Record lastReturned;

        protected ReadOnlyRecordIterator(Collection<Record> values) {
            this.iterator = values.iterator();

            advance();
        }

        @Override
        public boolean hasNext() {
            return nextRecord != null;
        }

        @Override
        public Record next() {
            if (nextRecord == null) {
                throw new NoSuchElementException();
            }
            lastReturned = nextRecord;
            advance();
            return lastReturned;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove not supported by this iterator");
        }

        private void advance() {
            while (iterator.hasNext()) {
                nextRecord = iterator.next();
                boolean reachable = isReachable(nextRecord);
                if (reachable && nextRecord != null) {
                    return;
                }
            }
            nextRecord = null;
        }
    }
}
TOP

Related Classes of com.hazelcast.map.AbstractEvictableRecordStore

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.