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

net.sf.ehcache.store.FrontEndCacheTier Maven / Gradle / Ivy

/**
 *  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;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import net.sf.ehcache.concurrent.LockType;
import net.sf.ehcache.concurrent.ReadWriteLockSync;
import net.sf.ehcache.concurrent.StripedReadWriteLock;
import net.sf.ehcache.concurrent.StripedReadWriteLockSync;
import net.sf.ehcache.search.impl.SearchManager;
import net.sf.ehcache.store.compound.ReadWriteCopyStrategy;
import net.sf.ehcache.util.SetAsList;
import net.sf.ehcache.writer.CacheWriterManager;

/**
 * Abstract class for stores which combine two other stores, one caching the other (aka authority)'s elements.
 *
 * @param  the cache tier store type
 * @param  the authority tier store type
 * @author Chris Dennis
 * @author Ludovic Orban
 */
public abstract class FrontEndCacheTier extends AbstractStore {

    private static final int DEFAULT_LOCK_STRIPE_COUNT = 128;

    /**
     * The cache tier store
     */
    protected final T cache;

    /**
     * The authority tier store
     */
    protected final U authority;

    private final StripedReadWriteLock masterLocks;
    private final boolean copyOnRead;
    private final boolean copyOnWrite;
    private final ReadWriteCopyStrategy copyStrategy;
    private final ConcurrentMap faults = new ConcurrentHashMap();

    /**
     * Constructor for FrontEndCacheTier
     *
     * @param cache the caching tier
     * @param authority the authority tier
     * @param copyStrategy the copyStrategy to use
     * @param searchManager the search manager to use
     * @param copyOnWrite whether to copy on writes, false otherwise
     * @param copyOnRead whether to copy on reads, false otherwise
     */
    public FrontEndCacheTier(T cache, U authority, ReadWriteCopyStrategy copyStrategy, SearchManager searchManager,
                             boolean copyOnWrite, boolean copyOnRead) {
        super(searchManager);
        this.cache = cache;
        this.authority = authority;
        this.copyStrategy = copyStrategy;
        this.copyOnWrite = copyOnWrite;
        this.copyOnRead = copyOnRead;
        if (authority instanceof StripedReadWriteLockProvider) {
            masterLocks = ((StripedReadWriteLockProvider) authority).createStripedReadWriteLock();
        } else {
            masterLocks = new StripedReadWriteLockSync(DEFAULT_LOCK_STRIPE_COUNT);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void unpinAll() {
        writeLock();
        try {
            // unpin in both authority first then cache later
            authority.unpinAll();
            cache.unpinAll();
        } finally {
            writeUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean isPinned(Object key) {
        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            // checking in authority first then cache later
           return authority.isPinned(key) || cache.isPinned(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void setPinned(Object key, boolean pinned) {
        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            // setting pinning in both authority first then cache later
            authority.setPinned(key, pinned);
            cache.setPinned(key, pinned);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Perform copy on read on an element if configured
     *
     * @param element the element to copy for read
     * @return a copy of the element with the reconstructed original value
     */
    protected Element copyElementForReadIfNeeded(Element element) {
        if (copyOnRead && copyOnWrite) {
            return copyStrategy.copyForRead(element);
        } else if (copyOnRead) {
            return copyStrategy.copyForRead(copyStrategy.copyForWrite(element));
        } else {
            return element;
        }
    }

    /**
     * Perform copy on write on an element if configured
     *
     * @param element the element to copy for write
     * @return a copy of the element with a storage-ready value
     */
    protected Element copyElementForWriteIfNeeded(Element element) {
        if (copyOnRead && copyOnWrite) {
            return copyStrategy.copyForWrite(element);
        } else if (copyOnWrite) {
            return copyStrategy.copyForRead(copyStrategy.copyForWrite(element));
        } else {
            return element;
        }
    }

    /**
     * Perform copy for the element If both copy on read and copy on write are set to true
     *
     * @param element the element to copy for removal
     * @return a copy of the element with a storage-ready value
     */
    private Element copyElementForRemovalIfNeeded(Element element) {
        if (copyOnRead && copyOnWrite) {
            return copyStrategy.copyForWrite(element);
        } else {
            return element;
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element get(Object key) {
        if (key == null) {
            return null;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            Element e = cache.get(key);
            if (e == null) {
                Fault fault = faults.putIfAbsent(key, new Fault());
                if (fault == null) {
                    try {
                        e = cache.get(key);
                        if (e != null) {
                          return copyElementForReadIfNeeded(e);
                        }
                        e = authority.get(key);
                        if (e != null) {
                            cache.put(e);
                        }
                        return copyElementForReadIfNeeded(e);
                    } finally {
                        faults.remove(key).complete(e);
                    }
                } else {
                    return copyElementForReadIfNeeded(fault.get());
                }
            } else {
                return copyElementForReadIfNeeded(e);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element getQuiet(Object key) {
        if (key == null) {
            return null;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            Element e = cache.getQuiet(key);
            if (e == null) {
                Fault fault = faults.putIfAbsent(key, new Fault());
                if (fault == null) {
                    try {
                        e = authority.getQuiet(key);
                        if (e != null) {
                            cache.put(e);
                        }
                    } finally {
                        faults.remove(key).complete(e);
                    }
                } else {
                    e = fault.get();
                }
            }
            return copyElementForReadIfNeeded(e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean put(Element e) {
        if (e == null) {
            return true;
        }

        Object key = e.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element copy = copyElementForWriteIfNeeded(e);
            final boolean put = authority.put(copy);
            try {
                cache.fill(copy);
            } catch (OutOfMemoryError oome) {
                authority.remove(e.getKey());
                throw oome;
            }
            return put;
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean putWithWriter(Element e, CacheWriterManager writer) {
        if (e == null) {
            return true;
        }

        Object key = e.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element copy = copyElementForWriteIfNeeded(e);
            final boolean put = authority.putWithWriter(copy, writer);
            try {
                cache.fill(copy);
            } catch (OutOfMemoryError oome) {
                authority.remove(e.getKey());
                throw oome;
            }
            return put;
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element remove(Object key) {
        if (key == null) {
            return null;
        }

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element e = cache.remove(key);
            if (e == null) {
                return copyElementForReadIfNeeded(authority.remove(key));
            } else {
                authority.removeNoReturn(key);
                return copyElementForReadIfNeeded(e);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element removeWithWriter(Object key, CacheWriterManager writerManager) throws CacheException {
        if (key == null) {
            return null;
        }

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            cache.remove(key);
            return copyElementForReadIfNeeded(authority.removeWithWriter(key, writerManager));
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element putIfAbsent(Element e) throws NullPointerException {
        Object key = e.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element copy = copyElementForWriteIfNeeded(e);
            Element old = authority.putIfAbsent(copy);
            if (old == null) {
                try {
                    cache.fill(copy);
                } catch (OutOfMemoryError oome) {
                    authority.remove(copy.getKey());
                    throw oome;
                }
            }
            return copyElementForReadIfNeeded(old);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element removeElement(Element e, ElementValueComparator comparator) throws NullPointerException {
        Object key = e.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            cache.remove(e.getObjectKey());
            return copyElementForReadIfNeeded(authority.removeElement(copyElementForRemovalIfNeeded(e), comparator));
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean replace(Element old, Element e, ElementValueComparator comparator) throws NullPointerException, IllegalArgumentException {
        Object key = old.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element copy = copyElementForWriteIfNeeded(e);
            cache.remove(old.getObjectKey());
                return authority.replace(copyElementForRemovalIfNeeded(old), copy, comparator);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public Element replace(Element e) throws NullPointerException {
        Object key = e.getObjectKey();

        Lock lock = getLockFor(key).writeLock();
        lock.lock();
        try {
            Element copy = copyElementForWriteIfNeeded(e);
            cache.remove(e.getObjectKey());
            return copyElementForReadIfNeeded(authority.replace(copy));
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKey(Object key) {
        if (key == null) {
            return false;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            return cache.containsKey(key) || authority.containsKey(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKeyOnDisk(Object key) {
        if (key == null) {
            return false;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            return cache.containsKeyOnDisk(key) || authority.containsKeyOnDisk(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKeyOffHeap(Object key) {
        if (key == null) {
            return false;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            return cache.containsKeyOffHeap(key) || authority.containsKeyOffHeap(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean containsKeyInMemory(Object key) {
        if (key == null) {
            return false;
        }

        Lock lock = getLockFor(key).readLock();
        lock.lock();
        try {
            return cache.containsKeyInMemory(key) || authority.containsKeyInMemory(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public List getKeys() {
        readLock();
        try {
            if (cache.isTierPinned() && !authority.isPersistent()) {
                return cache.getKeys();
            } else {
                return new SetAsList(
                    new CacheKeySet(
                        authority.getKeys(), cache.isTierPinned() ? cache.getKeys() : cache.getPresentPinnedKeys()));
            }
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void removeAll() throws CacheException {
        writeLock();
        try {
            cache.removeAll();
            authority.removeAll();
        } finally {
            writeUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void dispose() {
        cache.dispose();
        authority.dispose();
    }

    /**
     * {@inheritDoc}
     */
    public int getSize() {
        readLock();
        try {
            return Math.max(cache.getSize(), authority.getSize());
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getInMemorySize() {
        readLock();
        try {
            return authority.getInMemorySize() + cache.getInMemorySize();
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getOffHeapSize() {
        readLock();
        try {
            return authority.getOffHeapSize() + cache.getOffHeapSize();
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getOnDiskSize() {
        readLock();
        try {
            return authority.getOnDiskSize() + cache.getOnDiskSize();
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public int getTerracottaClusteredSize() {
        readLock();
        try {
            return authority.getTerracottaClusteredSize() + cache.getTerracottaClusteredSize();
        } finally {
            readUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    public long getInMemorySizeInBytes() {
        return authority.getInMemorySizeInBytes() + cache.getInMemorySizeInBytes();
    }

    /**
     * {@inheritDoc}
     */
    public long getOffHeapSizeInBytes() {
        return authority.getOffHeapSizeInBytes() + cache.getOffHeapSizeInBytes();
    }

    /**
     * {@inheritDoc}
     */
    public long getOnDiskSizeInBytes() {
        return authority.getOnDiskSizeInBytes() + cache.getOnDiskSizeInBytes();
    }

    /**
     * {@inheritDoc}
     */
    public void expireElements() {
        writeLock();
        try {
            authority.expireElements();
            cache.expireElements();
        } finally {
            writeUnlock();
        }
    }

    /**
     * {@inheritDoc}
     */
    //TODO : is this correct?
    public void flush() throws IOException {
        cache.flush();
        authority.flush();
    }

    /**
     * {@inheritDoc}
     */
    public boolean bufferFull() {
        return cache.bufferFull() || authority.bufferFull();
    }

    private void readLock() {
        for (ReadWriteLockSync lock : getAllLocks()) {
            lock.lock(LockType.READ);
        }
    }

    private void readUnlock() {
        for (ReadWriteLockSync lock : getAllLocks()) {
            lock.unlock(LockType.READ);
        }
    }

    private void writeLock() {
        for (ReadWriteLockSync lock : getAllLocks()) {
            lock.lock(LockType.WRITE);
        }
    }

    private void writeUnlock() {
        for (ReadWriteLockSync lock : getAllLocks()) {
            lock.unlock(LockType.WRITE);
        }
    }

    /**
     * Returns the ReadWriteLock guarding this key.
     *
     * @param key key of interest
     * @return lock for the supplied key
     */
    public ReadWriteLock getLockFor(Object key) {
        return masterLocks.getLockForKey(key);
    }

    /**
     * Returns all ReadWriteLockSyncs guarding this store.
     */
    protected List getAllLocks() {
        return masterLocks.getAllSyncs();
    }

    /**
     * {@inheritDoc}
     */
    public Status getStatus() {
        //TODO this might be wrong...
        return authority.getStatus();
    }

    /**
     * {@inheritDoc}
     */
    public Policy getInMemoryEvictionPolicy() {
        return cache.getInMemoryEvictionPolicy();
    }

    /**
     * {@inheritDoc}
     */
    public void setInMemoryEvictionPolicy(Policy policy) {
        cache.setInMemoryEvictionPolicy(policy);
    }

    /**
     * {@inheritDoc}
     */
    public final Object getInternalContext() {
        return masterLocks;
    }

    /**
     * Checks whether the element can be safely evicted.
     * It is done by checking whether we can lock the master lock for that Element's key and, if we could, remove that key from all other tiers,
     * but the lowest.
     * 

Failing to obey this, might result in firing an Element Evicted Event, while it is still present in higher tiers

* @param e The element we want to evict * @return true, if it can be evicted, false otherwise */ public boolean isEvictionCandidate(final Element e) { Object key = e.getObjectKey(); Lock lockForKey = masterLocks.getLockForKey(key).writeLock(); if (lockForKey.tryLock()) { try { cache.removeIfNotPinned(key); return true; } finally { lockForKey.unlock(); } } else { return false; } } /** * Checks whether the key is held in the fronting cache * @param key the key to check for * @return true if cached, false otherwise */ public boolean isCached(final Object key) { Lock lock = getLockFor(key).readLock(); lock.lock(); try { return cache.containsKey(key); } finally { lock.unlock(); } } /** * Whether evicting this from the cache should fire when evicting from upper tiers * * @param key the key to the element * @return true if we should fire, otherwise false */ public boolean notifyEvictionFromCache(final Serializable key) { return false; } /** * {@inheritDoc} */ @Override public boolean hasAbortedSizeOf() { return cache.hasAbortedSizeOf() || authority.hasAbortedSizeOf(); } @Override public void recalculateSize(Object key) { Lock lock = getLockFor(key).writeLock(); lock.lock(); try { authority.recalculateSize(key); cache.recalculateSize(key); } finally { lock.unlock(); } } /** * Type on which multiple concurrent authority fetches will wait. */ private static final class Fault { private Element result; private boolean complete; public Element get() { boolean interrupted = false; try { synchronized (this) { while (!complete) { try { wait(); } catch (InterruptedException ex) { interrupted = true; } } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } return result; } public void complete(Element e) { synchronized (this) { result = e; complete = true; notifyAll(); } } } }