Package net.sf.ehcache.store.compound.factories

Source Code of net.sf.ehcache.store.compound.factories.DiskStorageFactory$DiskMarker

/**
*  Copyright 2003-2010 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.compound.factories;

import net.sf.ehcache.concurrent.ConcurrencyUtil;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.RandomAccessFile;
import java.io.Serializable;

import java.util.ConcurrentModificationException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import net.sf.ehcache.event.RegisteredEventListeners;
import net.sf.ehcache.store.compound.CompoundStore;
import net.sf.ehcache.store.compound.ElementSubstitute;
import net.sf.ehcache.store.compound.ElementSubstituteFactory;
import net.sf.ehcache.util.MemoryEfficientByteArrayOutputStream;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

/**
* A mock-up of a on-disk element proxy factory.
*
* @param <T> type of the encoded element substitutes
* @author Chris Dennis
*/
abstract class DiskStorageFactory<T extends ElementSubstitute> implements ElementSubstituteFactory<T> {

    /**
     * Path stub used to create unique ehcache directories.
     */
    protected static final String AUTO_DISK_PATH_DIRECTORY_PREFIX = "ehcache_auto_created";
    private static final int SERIALIZATION_CONCURRENCY_DELAY = 250;
    private static final int SHUTDOWN_GRACE_PERIOD = 60;
    private static final int MEGABYTE = 1024 * 1024;
   
    private static final Logger LOG = LoggerFactory.getLogger(DiskStorageFactory.class.getName());

    /**
     * The store bound to this factory.
     */
    protected volatile CompoundStore                  store;
   
    private final BlockingQueue<Runnable> diskQueue;
    /**
     * Executor service used to write elements to disk
     */
    private final ScheduledThreadPoolExecutor diskWriter;
   
    private final long queueCapacity;
   
    private final File             file;
    private final RandomAccessFile[] dataAccess;

    private final FileAllocationTree allocator;

    private final RegisteredEventListeners eventService;

    private volatile int elementSize;
   
    /**
     * Constructs a disk storage factory using the given data file.
     *
     * @param dataFile
     */
    DiskStorageFactory(File dataFile, long expiryInterval, long queueCapacity,
        RegisteredEventListeners eventService, final boolean daemonWriter, int stripes) {
        this.file = dataFile;
        try {
            dataAccess = allocateRandomAccessFiles(file, stripes);
        } catch (FileNotFoundException e) {
            throw new CacheException(e);
        }
        this.allocator = new FileAllocationTree(Long.MAX_VALUE, dataAccess[0]);
       
        diskWriter = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, file.getName());
                t.setDaemon(daemonWriter);
                return t;
            }
        });
        this.diskQueue = diskWriter.getQueue();
        this.eventService = eventService;
        this.queueCapacity = queueCapacity * MEGABYTE;
       
        diskWriter.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        diskWriter.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
        diskWriter.scheduleWithFixedDelay(new DiskExpiryTask(), expiryInterval, expiryInterval, TimeUnit.SECONDS);
    }
   
    private static RandomAccessFile[] allocateRandomAccessFiles(File f, int stripes) throws FileNotFoundException {
        int roundedStripes = stripes;
        while ((roundedStripes & (roundedStripes - 1)) != 0) {
            ++roundedStripes;
        }

        RandomAccessFile [] result = new RandomAccessFile[roundedStripes];
        for (int i = 0; i < result.length; ++i) {
            result[i] = new RandomAccessFile(f, "rw");
        }

        return result;
    }

    private RandomAccessFile getDataAccess(Object key) {
        return this.dataAccess[ConcurrencyUtil.selectLock(key, dataAccess.length)];
    }

    /**
     * Return this size in bytes of this factory
     */
    public long getOnDiskSizeInBytes() {
        synchronized (dataAccess[0]) {
            try {
                return dataAccess[0].length();
            } catch (IOException e) {
                LOG.warn("Exception trying to determine store size", e);
                return 0;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void bind(CompoundStore store) {
        this.store = store;
    }

    /**
     * {@inheritDoc}
     */
    public void free(Lock lock, ElementSubstitute substitute) {
        if (substitute instanceof DiskStorageFactory.DiskMarker) {
            //free done asynchronously under the relevant segment lock...
            DiskFreeTask free = new DiskFreeTask(lock, (DiskMarker) substitute);
            if (lock.tryLock()) {
                try {
                    free.call();
                } finally {
                    lock.unlock();
                }
            } else {
                schedule(free);
            }
        }
    }
   
    /**
     * Mark this on-disk marker as used (hooks into the file space allocation structure).
     */
    protected void markUsed(DiskMarker marker) {
        allocator.mark(new Region(marker.getPosition(), marker.getPosition() + marker.getSize() - 1));
    }

    /**
     * Shrink this store's data file down to a minimal size for its contents.
     */
    protected void shrinkDataFile() {
        synchronized (dataAccess[0]) {
            try {
                dataAccess[0].setLength(allocator.getFileSize());
            } catch (IOException e) {
                LOG.error("Exception trying to shrink data file to size", e);
            }
        }
    }
    /**
     * Shuts down this disk factory.
     * <p>
     * This shuts down the executor and then waits for its termination, before closing the data file.
     */
    protected void shutdown() throws IOException {
        diskWriter.shutdown();
        for (int i = 0; i < SHUTDOWN_GRACE_PERIOD; i++) {
            try {
                if (diskWriter.awaitTermination(1, TimeUnit.SECONDS)) {
                    break;
                } else {
                    LOG.info("Waited " + (i + 1) + " seconds for shutdown of [" + file.getName() + "]");
                }
            } catch (InterruptedException e) {
                LOG.warn("Received exception while waiting for shutdown", e);
            }
        }

        for (RandomAccessFile raf : dataAccess) {
            synchronized (raf) {
                raf.close();
            }
        }
    }
   
    /**
     * Deletes the data file for this factory.
     */
    protected void delete() {
        file.delete();
        allocator.clear();
        if (file.getAbsolutePath().contains(AUTO_DISK_PATH_DIRECTORY_PREFIX)) {
            //try to delete the auto_createtimestamp directory. Will work when the last Disk Store deletes
            //the last files and the directory becomes empty.
            File dataDirectory = file.getParentFile();
            if (dataDirectory != null && dataDirectory.exists()) {
                if (dataDirectory.delete()) {
                    LOG.debug("Deleted directory " + dataDirectory.getName());
                }
            }

        }
    }

    /**
     * Schedule to given task on the disk writer executor service.
     *
     * @param <U> return type of the callable
     * @param call callable to call
     * @return Future representing the return of this call
     */
    protected <U> Future<U> schedule(Callable<U> call) {
        return diskWriter.submit(call);
    }

    /**
     * Read the data at the given marker, and return the associated deserialized Element.
     *
     * @param marker marker to read
     * @return deserialized Element
     * @throws IOException on read error
     * @throws ClassNotFoundException on deserialization error
     */
    protected Element read(DiskMarker marker) throws IOException, ClassNotFoundException {
        final byte[] buffer = new byte[marker.getSize()];
        RandomAccessFile data = getDataAccess(marker.getKey());
        synchronized (data) {
            // Load the element
            data.seek(marker.getPosition());
            data.readFully(buffer);
        }
       
        ObjectInputStream objstr = new ObjectInputStream(new ByteArrayInputStream(buffer)) {
            /**
             * Overridden because of:
             * Bug 1324221 ehcache DiskStore has issues when used in Tomcat
             */
            @Override
            protected Class resolveClass(ObjectStreamClass clazz) throws ClassNotFoundException, IOException {
                try {
                    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                    return Class.forName(clazz.getName(), false, classLoader);
                } catch (ClassNotFoundException e) {
                    // Use the default as a fallback because of
                    // bug 1517565 - DiskStore loadElementFromDiskElement
                    return super.resolveClass(clazz);
                }
            }
        };
       
        try {
            return (Element) objstr.readObject();
        } finally {
            objstr.close();
        }
    }

    /**
     * Write the given element to disk, and return the associated marker.
     *
     * @param element to write
     * @return marker representing the element
     * @throws IOException on write error
     */
    protected DiskMarker write(Element element) throws IOException {
        MemoryEfficientByteArrayOutputStream buffer = serializeElement(element);
        int bufferLength = buffer.size();
        elementSize = bufferLength;
        DiskMarker marker = alloc(element, bufferLength);
        // Write the record
        RandomAccessFile data = getDataAccess(element.getObjectKey());
        synchronized (data) {
            data.seek(marker.getPosition());
            data.write(buffer.toByteArray(), 0, bufferLength);
        }
        return marker;
    }

    private MemoryEfficientByteArrayOutputStream serializeElement(Element element) throws IOException {
        // try two times to Serialize. A ConcurrentModificationException can occur because Java's serialization
        // mechanism is not threadsafe and POJOs are seldom implemented in a threadsafe way.
        // e.g. we are serializing an ArrayList field while another thread somewhere in the application is appending to it.
        // The best we can do is try again and then give up.
        ConcurrentModificationException exception = null;
        for (int retryCount = 0; retryCount < 2; retryCount++) {
            try {
                return MemoryEfficientByteArrayOutputStream.serialize(element);
            } catch (ConcurrentModificationException e) {
                exception = e;
                try {
                    // wait for the other thread(s) to finish
                    MILLISECONDS.sleep(SERIALIZATION_CONCURRENCY_DELAY);
                } catch (InterruptedException e1) {
                    //no-op
                }
            }
        }
        throw exception;
    }

    private DiskMarker alloc(Element element, int size) throws IOException {
        //check for a matching chunk
        Region r = allocator.alloc(size);
        return createMarker(r.start(), size, element);
    }
   
    /**
     * Create a disk marker representing the given element, and area on disk.
     * <p>
     * This method can be overridden by subclasses to use different marker types.
     *
     * @param position starting disk offset
     * @param size size of in disk area
     * @param element element to be written to area
     * @return marker representing the element.
     */
    protected DiskMarker createMarker(long position, int size, Element element) {
        return new OverflowDiskMarker(this, position, size, element);
    }
   
    /**
     * Free the given marker to be used by a subsequent write.
     *
     * @param marker marker to be free'd
     */
    protected void free(DiskMarker marker) {
        allocator.free(new Region(marker.getPosition(), marker.getPosition() + marker.getSize() - 1));
    }

    /**
     * Return {@code true} if the disk write queue is full.
     */
    public boolean bufferFull() {
        return (diskQueue.size() * elementSize) > queueCapacity;
    }

    /**
     * Return a reference to the data file backing this factory.
     */
    public File getDataFile() {
        return file;
    }
   
    /**
     * DiskWriteTasks are used to serialize elements
     * to disk and fault in the resultant DiskMarker
     * instance.
     */
    abstract class DiskWriteTask implements Callable<DiskMarker> {

        private final Placeholder placeholder;
       
        /**
         * Create a disk-write task for the given placeholder.
         */
        DiskWriteTask(Placeholder p) {
            this.placeholder = p;
        }

        /**
         * Return the placeholder that this task will write.
         */
        Placeholder getPlaceholder() {
            return placeholder;
        }
       
        /**
         * {@inheritDoc}
         */
        public DiskMarker call() {
            try {
                DiskMarker marker = write(placeholder.getElement());
                if (store.fault(placeholder.getKey(), placeholder, marker)) {
                    return marker;
                } else {
                    return null;
                }
            } catch (IOException e) {
                LOG.error("Disk Write of " + placeholder.getKey() + " failed (it will be evicted instead): ", e);
                store.evict(placeholder.getKey(), placeholder);
                return null;
            }
        }
    }

    /**
     * Disk free tasks are used to asynchronously free DiskMarker instances under the correct
     * exclusive write lock.  This ensure markers are not free'd until no more readers can be
     * holding references to them.
     */
    private final class DiskFreeTask implements Callable<Void> {
        private final Lock lock;
        private final DiskMarker marker;
       
        private DiskFreeTask(Lock lock, DiskMarker marker) {
            this.lock = lock;
            this.marker = marker;
        }

        /**
         * {@inheritDoc}
         */
        public Void call() {
            lock.lock();
            try {
                DiskStorageFactory.this.free(marker);
            } finally {
                lock.unlock();
            }
            return null;
        }
    }

    /**
     * Abstract superclass for all disk substitutes.
     */
    abstract static class DiskSubstitute implements ElementSubstitute {

        private transient volatile DiskStorageFactory<? extends ElementSubstitute> factory;
       
        /**
         * Create a disk subsitute bound to no factory.  This constructor is used during
         * de-serialization.
         */
        public DiskSubstitute() {
            this.factory = null;
        }

        /**
         * Create a disk subsitute bound to the given factory.
         */
        DiskSubstitute(DiskStorageFactory<? extends ElementSubstitute> factory) {
            this.factory = factory;
        }

        /**
         * Return the key to which this marker is (or should be) mapped.
         */
        abstract Object getKey();

        /**
         * Return the total number of hits on this marker
         */
        abstract long getHitCount();
       
        /**
         * Return the time at which this marker expires.
         */
        abstract long getExpirationTime();

        /**
         * {@inheritDoc}
         */
        public final DiskStorageFactory<ElementSubstitute> getFactory() {
            return (DiskStorageFactory<ElementSubstitute>) factory;
        }
       
        /**
         * Bind this marker to a given factory.
         * <p>
         * Used during deserialization of markers to associate them with the deserializing factory.
         */
        void bindFactory(DiskStorageFactory<? extends ElementSubstitute> factory) {
            this.factory = factory;
        }
    }
   
    /**
     * Placeholder instances are put in place to prevent
     * duplicate write requests while Elements are being
     * written to disk.
     */
    abstract static class Placeholder extends DiskSubstitute {
        private final Object key;
        private final Element element;
       
        /**
         * Create a Placeholder wrapping the given element and key.
         */
        Placeholder(DiskStorageFactory<ElementSubstitute> factory, Element element) {
            super(factory);
            this.key = element.getObjectKey();
            this.element = element;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        Object getKey() {
            return key;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        long getHitCount() {
            return getElement().getHitCount();
        }
       
        @Override
        long getExpirationTime() {
            return getElement().getExpirationTime();
        }

        /**
         * Return the element that this Placeholder is wrapping.
         */
        Element getElement() {
            return element;
        }       
    }
   
    /**
     * Overflow specific disk marker implementation.
     */
    private final static class OverflowDiskMarker extends DiskMarker {

        private final long expiry;

        OverflowDiskMarker(DiskStorageFactory<? extends ElementSubstitute> factory, long position, int size, Element element) {
            super(factory, position, size, element);
            this.expiry = element.getExpirationTime();
        }

        OverflowDiskMarker(DiskStorageFactory<? extends ElementSubstitute> factory, long position, int size, Object key, long hits,
                long expiry) {
            super(factory, position, size, key, hits);
            this.expiry = expiry;
        }

        @Override
        long getExpirationTime() {
            return expiry;
        }
    }

    /**
     * DiskMarker instances point to the location of their
     * associated serialized Element instance.
     */
    static abstract class DiskMarker extends DiskSubstitute implements Serializable {
       
        private final Object key;
       
        private final long position;
        private final int size;
       
        private final long hitCount;
       
        /**
         * Create a new marker tied to the given factory instance.
         *
         * @param factory factory responsible for this marker
         * @param position position on disk where the element will be stored
         * @param size size of the serialized element
         * @param element element being stored
         */
        DiskMarker(DiskStorageFactory<? extends ElementSubstitute> factory, long position, int size, Element element) {
            super(factory);
            this.position = position;
            this.size = size;
           
            this.key = element.getObjectKey();
            this.hitCount = element.getHitCount();
        }

        /**
         * Create a new marker tied to the given factory instance.
         *
         * @param factory factory responsible for this marker
         * @param position position on disk where the element will be stored
         * @param size size of the serialized element
         * @param key key to which this element is mapped
         * @param hits hit count for this element
         */
        DiskMarker(DiskStorageFactory<? extends ElementSubstitute> factory, long position, int size, Object key, long hits) {
            super(factory);
            this.position = position;
            this.size = size;
           
            this.key = key;
            this.hitCount = hits;
        }
       
        /**
         * Key to which this Element is mapped.
         *
         * @return key for this Element
         */
        @Override
        Object getKey() {
            return key;
        }

        /**
         * Number of hits on this Element.
         */
        @Override
        long getHitCount() {
            return hitCount;
        }
       
        /**
         * Disk offset at which this element is stored.
         *
         * @return disk offset
         */
        private long getPosition() {
            return position;
        }

        /**
         * Returns the size of the currently occupying element.
         *
         * @return size of the stored element
         */
        private int getSize() {
            return size;
        }

        /**
         * {@inheritDoc}
         * <p>
         * A No-Op
         */
        public void installed() {
            //no-op
        }       
    }


    /**
     * Remove elements created by this factory if they have expired.
     */
    public void expireElements() {
        new DiskExpiryTask().run();
    }
   
    /**
     * Causes removal of all expired elements (and fires the relevant events).
     */
    private final class DiskExpiryTask implements Runnable {
       
        /**
         * {@inheritDoc}
         */
        public void run() {
            long now = System.currentTimeMillis();
            for (Object key : store.keySet()) {
                Object value = store.unretrievedGet(key);
                if (created(value) && value instanceof DiskStorageFactory.DiskMarker) {
                    checkExpiry((DiskMarker) value, now);
                }
            }
        }
       
        private void checkExpiry(DiskMarker marker, long now) {
            if (marker.getExpirationTime() < now) {
                if (eventService.hasCacheEventListeners()) {
                    try {
                        Element element = read(marker);
                        if (store.evict(marker.getKey(), marker)) {
                            eventService.notifyElementExpiry(element, false);
                        }
                    } catch (Exception e) {
                        return;
                    }
                } else {
                    store.evict(marker.getKey(), marker);
                }
            }
        }
    }
}
TOP

Related Classes of net.sf.ehcache.store.compound.factories.DiskStorageFactory$DiskMarker

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.