All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.ehcache.store.cachingtier.PooledBasedBackEnd Maven / Gradle / Ivy

Go to download

Ehcache is an open source, standards-based cache used to boost performance, offload the database and simplify scalability. Ehcache is robust, proven and full-featured and this has made it the most widely-used Java-based cache.

There is a newer version: 2.10.9.2
Show newest version
/**
 *  Copyright Terracotta, Inc.
 *
 *  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 net.sf.ehcache.store.cachingtier;

import net.sf.ehcache.Element;
import net.sf.ehcache.pool.PoolAccessor;
import net.sf.ehcache.store.Policy;
import net.sf.ehcache.util.concurrent.ConcurrentHashMap;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.statistics.observer.OperationObserver;

import net.sf.ehcache.store.StoreOperationOutcomes.GetOutcome;
import org.terracotta.statistics.OperationStatistic;
import org.terracotta.statistics.StatisticsManager;
import org.terracotta.statistics.derived.EventRateSimpleMovingAverage;
import org.terracotta.statistics.derived.OperationResultFilter;

import static net.sf.ehcache.statistics.StatisticBuilder.operation;

/**
 * A backend to a OnHeapCachingTier that will be cap'ed using a pool
 *
 * @param  the key type
 * @param  the value type
 *
 * @author Alex Snaps
 */
public class PooledBasedBackEnd extends ConcurrentHashMap implements HeapCacheBackEnd {

    private static final Logger LOG = LoggerFactory.getLogger(CountBasedBackEnd.class.getName());

    private static final int MAX_EVICTIONS = 5;
    private static final float PUT_LOAD_THRESHOLD = 0.9f;

    private volatile Policy policy;
    private volatile RemovalCallback callback;
    private final AtomicReference poolAccessor = new AtomicReference();

    private final OperationObserver getObserver = operation(GetOutcome.class).named("arc-get").of(this).tag("private").build();

    /**
     * Constructs a Pooled backend
     * @param memoryEvictionPolicy the policy it'll use to decide what to evict
     */
    public PooledBasedBackEnd(final Policy memoryEvictionPolicy) {
        setPolicy(memoryEvictionPolicy);
    }

    @Override
    public V putIfAbsent(final K key, final V value) {
        long delta = poolAccessor.get().add(key, value, FAKE_TREE_NODE, false);
        if (delta > -1) {
            final V previous = (V)super.internalPutIfAbsent(key, value, delta > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)delta);
            if (previous != null) {
                poolAccessor.get().delete(delta);
            }
            return previous;
        } else {
            final RemovalCallback cb = callback;
            if (cb != null) {
                cb.removed(key, value);
            }
            return null;
        }
    }

    @Override
    public V get(final Object key) {
        getObserver.begin();
        final V value = super.get(key);
        if (value != null) {
            getObserver.end(GetOutcome.HIT);
        } else {
            getObserver.end(GetOutcome.MISS);
        }
        return value;
    }

    @Override
    public void putAll(final Map m) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V put(final K key, final V value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public V remove(final Object key) {
        return super.removeAndNotify(key, callback);
    }

    @Override
    public boolean remove(final Object key, final Object value) {
        return super.remove(key, value);
    }

    @Override
    public boolean replace(final K key, final V oldValue, final V newValue) {
        return super.replace(key, oldValue, newValue);
    }

    @Override
    public V replace(final K key, final V value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear(final boolean notify) {
        if (notify) {
            for (Map.Entry entry : entrySet()) {
                if (entry.getValue() instanceof Element) {
                    removeAndNotify(entry.getKey(), entry.getValue(), callback);
                }
            }
        } else {
            super.clear();
        }
    }

    @Override
    public boolean hasSpace() {
        PoolAccessor accessor = poolAccessor.get();
        return accessor.getPoolOccupancy() < (PUT_LOAD_THRESHOLD * accessor.getPoolSize());
    }

    /**
     * tries to evict as many entries as specified
     * @param evictions amount of entries to be evicted
     * @return return true if exactly the right amount of evictions could happen, false otherwise
     */
    public boolean evict(int evictions) {
        while (evictions-- > 0) {
            final Element evictionCandidate = findEvictionCandidate();
            if (evictionCandidate != null) {
                remove(evictionCandidate.getObjectKey(), evictionCandidate, callback);
            } else {
                return false;
            }
        }
        return true;
    }

    private Element findEvictionCandidate() {
        List values = getRandomValues(MAX_EVICTIONS);
        // this can return null. Let the cache get bigger by one.
        List elements = new ArrayList(values.size() * 2);
        for (V v : values) {
            if (v instanceof Element) {
                elements.add((Element)v);
            }
        }
        return policy.selectedBasedOnPolicy(elements.toArray(new Element[elements.size()]), null);
    }

    /**
     * Dynamic property to switch the policy out
     * @param policy the new eviction Policy to use
     */
    public void setPolicy(final Policy policy) {
        if (policy == null) {
            throw new NullPointerException("We need a Policy passed in here, null won't cut it!");
        }
        this.policy = policy;
    }

    @Override
    public void registerEvictionCallback(final EvictionCallback evictionCallback) {
        this.callback = evictionCallback == null ? null : new RemovalCallback() {
            @Override
            public void removed(final Object key, final Object value) {
                evictionCallback.evicted((K)key, (V)value);
            }
        };
    }

    @Override
    public Policy getPolicy() {
        return policy;
    }

    /**
     * Registers the accessor with the backend. This can only happen once!
     * @param poolAccessor the pool accessor to use
     */
    public void registerAccessor(final PoolAccessor poolAccessor) {
        if (poolAccessor == null) {
            throw new NullPointerException("No null poolAccessor allowed here!");
        }
        if (!this.poolAccessor.compareAndSet(null, poolAccessor)) {
            throw new IllegalStateException("Can't set the poolAccessor multiple times!");
        }
        super.setPoolAccessor(poolAccessor);
    }

    /**
     * Returns the size in bytes
     * @return the amount of bytes for this backend
     */
    @Deprecated
    public long getSizeInBytes() {
        return poolAccessor.get().getSize();
    }

    /**
     * A pool participant to use with this Backend
     */
    public static class PoolParticipant implements net.sf.ehcache.pool.PoolParticipant {

        private final EventRateSimpleMovingAverage hitRate = new EventRateSimpleMovingAverage(1, TimeUnit.SECONDS);
        private final EventRateSimpleMovingAverage missRate = new EventRateSimpleMovingAverage(1, TimeUnit.SECONDS);
        private final PooledBasedBackEnd pooledBasedBackEnd;

        /**
         * Creates a pool participant
         * @param pooledBasedBackEnd the backend this participant represents
         */
        public PoolParticipant(final PooledBasedBackEnd pooledBasedBackEnd) {
            this.pooledBasedBackEnd = pooledBasedBackEnd;
            OperationStatistic getStatistic = StatisticsManager.getOperationStatisticFor(pooledBasedBackEnd.getObserver);
            getStatistic.addDerivedStatistic(new OperationResultFilter(EnumSet.of(GetOutcome.HIT), hitRate));
            getStatistic.addDerivedStatistic(new OperationResultFilter(EnumSet.of(GetOutcome.MISS), missRate));
        }

        @Override
        public boolean evict(final int count, final long size) {
            try {
                return pooledBasedBackEnd.evict(count);
            } catch (Throwable e) {
                LOG.warn("Caught throwable while evicting", e);
            }
            return false;
        }

        @Override
        public float getApproximateHitRate() {
            return hitRate.rateUsingSeconds().floatValue();
        }

        @Override
        public float getApproximateMissRate() {
            return missRate.rateUsingSeconds().floatValue();
        }

        @Override
        public long getApproximateCountSize() {
            return pooledBasedBackEnd.mappingCount();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy