Index: ehcache-core/src/main/java/net/sf/ehcache/store/disk/DiskStorageFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>/**\n *  Copyright Terracotta, Inc.\n *\n *  Licensed under the Apache License, Version 2.0 (the \"License\");\n *  you may not use this file except in compliance with the License.\n *  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n *  Unless required by applicable law or agreed to in writing, software\n *  distributed under the License is distributed on an \"AS IS\" BASIS,\n *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *  See the License for the specific language governing permissions and\n *  limitations under the License.\n */\n\npackage net.sf.ehcache.store.disk;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.ObjectInputStream;\nimport java.io.ObjectOutputStream;\nimport java.io.RandomAccessFile;\nimport java.io.Serializable;\nimport java.util.ConcurrentModificationException;\nimport java.util.List;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.ScheduledThreadPoolExecutor;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.locks.Lock;\n\nimport net.sf.ehcache.CacheException;\nimport net.sf.ehcache.DiskStorePathManager;\nimport net.sf.ehcache.Ehcache;\nimport net.sf.ehcache.Element;\nimport net.sf.ehcache.concurrent.ConcurrencyUtil;\nimport net.sf.ehcache.config.CacheConfiguration;\nimport net.sf.ehcache.config.PinningConfiguration;\nimport net.sf.ehcache.event.RegisteredEventListeners;\nimport net.sf.ehcache.pool.sizeof.annotations.IgnoreSizeOf;\nimport net.sf.ehcache.store.disk.ods.FileAllocationTree;\nimport net.sf.ehcache.store.disk.ods.Region;\nimport net.sf.ehcache.util.MemoryEfficientByteArrayOutputStream;\nimport net.sf.ehcache.util.PreferredLoaderObjectInputStream;\nimport net.sf.ehcache.util.TimeUtil;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * A mock-up of a on-disk element proxy factory.\n *\n * @author Chris Dennis\n * @author Ludovic Orban\n */\n@IgnoreSizeOf\npublic class DiskStorageFactory {\n\n    /**\n     * Path stub used to create unique ehcache directories.\n     */\n    private static final int SERIALIZATION_CONCURRENCY_DELAY = 250;\n    private static final int SHUTDOWN_GRACE_PERIOD = 60;\n    private static final int MEGABYTE = 1024 * 1024;\n    private static final int MAX_EVICT = 5;\n    private static final int SAMPLE_SIZE = 30;\n\n    private static final Logger LOG = LoggerFactory.getLogger(DiskStorageFactory.class.getName());\n\n    /**\n     * The store bound to this factory.\n     */\n    protected volatile DiskStore                  store;\n\n    private final BlockingQueue<Runnable> diskQueue;\n    /**\n     * Executor service used to write elements to disk\n     */\n    private final ScheduledThreadPoolExecutor diskWriter;\n\n    private final long queueCapacity;\n\n    private final File             file;\n    private final RandomAccessFile[] dataAccess;\n\n    private final FileAllocationTree allocator;\n\n    private final RegisteredEventListeners eventService;\n\n    private volatile int elementSize;\n\n    private final ElementSubstituteFilter onDiskFilter = new OnDiskFilter();\n\n    private final AtomicInteger onDisk = new AtomicInteger();\n\n    private final File indexFile;\n\n    private final IndexWriteTask flushTask;\n\n    private volatile int diskCapacity;\n\n    private volatile boolean pinningEnabled;\n\n    private final boolean diskPersistent;\n\n    private final DiskStorePathManager diskStorePathManager;\n    \n    private final ClassLoader classLoader;\n   \n    /**\n     * Constructs an disk persistent factory for the given cache and disk path.\n     *\n     * @param cache cache that fronts this factory\n     */\n    public DiskStorageFactory(Ehcache cache, RegisteredEventListeners cacheEventNotificationService) {\n        this.classLoader = cache.getCacheConfiguration().getClassLoader();       \n        this.diskStorePathManager = cache.getCacheManager().getDiskStorePathManager();\n        this.file = diskStorePathManager.getFile(cache.getName(), \".data\");\n\n        this.indexFile = diskStorePathManager.getFile(cache.getName(), \".index\");\n        this.pinningEnabled = determineCachePinned(cache.getCacheConfiguration());\n        this.diskPersistent = cache.getCacheConfiguration().isDiskPersistent();\n\n        if (diskPersistent && diskStorePathManager.isAutoCreated()) {\n            LOG.warn(\"Data in persistent disk stores is ignored for stores from automatically created directories.\\n\"\n                    + \"Remove diskPersistent or resolve the conflicting disk paths in cache configuration.\\n\"\n                    + \"Deleting data file \" + file.getAbsolutePath());\n            deleteFile(file);\n        } else if (!diskPersistent) {\n            deleteFile(file);\n            deleteFile(indexFile);\n        }\n\n        try {\n            dataAccess = allocateRandomAccessFiles(file, cache.getCacheConfiguration().getDiskAccessStripes());\n        } catch (FileNotFoundException e) {\n            throw new CacheException(e);\n        }\n        this.allocator = new FileAllocationTree(Long.MAX_VALUE, dataAccess[0]);\n\n        diskWriter = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {\n            public Thread newThread(Runnable r) {\n                Thread t = new Thread(r, file.getName());\n                t.setDaemon(false);\n                return t;\n            }\n        });\n        this.diskQueue = diskWriter.getQueue();\n        this.eventService = cache.getCacheEventNotificationService();\n        this.queueCapacity = cache.getCacheConfiguration().getDiskSpoolBufferSizeMB() * MEGABYTE;\n        this.diskCapacity = cache.getCacheConfiguration().getMaxElementsOnDisk();\n\n        diskWriter.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);\n        diskWriter.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);\n        long expiryInterval = cache.getCacheConfiguration().getDiskExpiryThreadIntervalSeconds();\n        diskWriter.scheduleWithFixedDelay(new DiskExpiryTask(), expiryInterval, expiryInterval, TimeUnit.SECONDS);\n\n        flushTask = new IndexWriteTask(indexFile, cache.getCacheConfiguration().isClearOnFlush());\n\n        if (!getDataFile().exists() || (getDataFile().length() == 0)) {\n            LOG.debug(\"Matching data file missing (or empty) for index file. Deleting index file \" + indexFile);\n            deleteFile(indexFile);\n        } else if (getDataFile().exists() && indexFile.exists()) {\n            if (getDataFile().lastModified() > (indexFile.lastModified() + TimeUnit.SECONDS.toMillis(1))) {\n                LOG.warn(\"The index for data file {} is out of date, probably due to an unclean shutdown. \"\n                        + \"Deleting index file {}\", getDataFile(), indexFile);\n                deleteFile(indexFile);\n            }\n        }\n    }\n\n    private boolean determineCachePinned(CacheConfiguration cacheConfiguration) {\n        PinningConfiguration pinningConfiguration = cacheConfiguration.getPinningConfiguration();\n        if (pinningConfiguration == null) {\n            return false;\n        }\n\n        switch (pinningConfiguration.getStore()) {\n            case LOCALMEMORY:\n                return false;\n\n            case INCACHE:\n                return true;\n\n            default:\n                throw new IllegalArgumentException();\n        }\n    }\n\n    private static RandomAccessFile[] allocateRandomAccessFiles(File f, int stripes) throws FileNotFoundException {\n        int roundedStripes = stripes;\n        while ((roundedStripes & (roundedStripes - 1)) != 0) {\n            ++roundedStripes;\n        }\n\n        RandomAccessFile [] result = new RandomAccessFile[roundedStripes];\n        for (int i = 0; i < result.length; ++i) {\n            result[i] = new RandomAccessFile(f, \"rw\");\n        }\n\n        return result;\n    }\n\n    private RandomAccessFile getDataAccess(Object key) {\n        return this.dataAccess[ConcurrencyUtil.selectLock(key, dataAccess.length)];\n    }\n\n    /**\n     * Return this size in bytes of this factory\n     *\n     * @return this size in bytes of this factory\n     */\n    public long getOnDiskSizeInBytes() {\n        synchronized (dataAccess[0]) {\n            try {\n                return dataAccess[0].length();\n            } catch (IOException e) {\n                LOG.warn(\"Exception trying to determine store size\", e);\n                return 0;\n            }\n        }\n    }\n\n    /**\n     * Bind a store instance to this factory.\n     *\n     * @param store store to bind\n     */\n    public void bind(DiskStore store) {\n        this.store = store;\n        loadIndex();\n    }\n\n    /**\n     * Free any manually managed resources used by this {@link DiskSubstitute}.\n     *\n     * @param lock the lock protecting the DiskSubstitute\n     * @param substitute DiskSubstitute being freed.\n     */\n    public void free(Lock lock, DiskSubstitute substitute) {\n        free(lock, substitute, false);\n    }\n\n    /**\n     * Free any manually managed resources used by this {@link DiskSubstitute}.\n     *\n     * @param lock the lock protecting the DiskSubstitute\n     * @param substitute DiskSubstitute being freed.\n     * @param faultFailure true if this DiskSubstitute should be freed because of a disk failure\n     */\n    public void free(Lock lock, DiskSubstitute substitute, boolean faultFailure) {\n        if (substitute instanceof DiskStorageFactory.DiskMarker) {\n            if (!faultFailure) {\n                onDisk.decrementAndGet();\n            }\n            //free done asynchronously under the relevant segment lock...\n            DiskFreeTask free = new DiskFreeTask(lock, (DiskMarker) substitute);\n            if (lock.tryLock()) {\n                try {\n                    free.call();\n                } finally {\n                    lock.unlock();\n                }\n            } else {\n                schedule(free);\n            }\n        }\n    }\n\n    /**\n     * Mark this on-disk marker as used (hooks into the file space allocation structure).\n     *\n     * @param marker on-disk marker to mark as used\n     */\n    protected void markUsed(DiskMarker marker) {\n        allocator.mark(new Region(marker.getPosition(), marker.getPosition() + marker.getSize() - 1));\n    }\n\n    /**\n     * Shrink this store's data file down to a minimal size for its contents.\n     */\n    protected void shrinkDataFile() {\n        synchronized (dataAccess[0]) {\n            try {\n                dataAccess[0].setLength(allocator.getFileSize());\n            } catch (IOException e) {\n                LOG.error(\"Exception trying to shrink data file to size\", e);\n            }\n        }\n    }\n    /**\n     * Shuts down this disk factory.\n     * <p>\n     * This shuts down the executor and then waits for its termination, before closing the data file.\n     * @throws java.io.IOException if an IO error occurred\n     */\n    protected void shutdown() throws IOException {\n        diskWriter.shutdown();\n        for (int i = 0; i < SHUTDOWN_GRACE_PERIOD; i++) {\n            try {\n                if (diskWriter.awaitTermination(1, TimeUnit.SECONDS)) {\n                    break;\n                } else {\n                    LOG.info(\"Waited \" + (i + 1) + \" seconds for shutdown of [\" + file.getName() + \"]\");\n                }\n            } catch (InterruptedException e) {\n                LOG.warn(\"Received exception while waiting for shutdown\", e);\n            }\n        }\n\n        for (final RandomAccessFile raf : dataAccess) {\n            synchronized (raf) {\n                raf.close();\n            }\n        }\n\n        if (!diskPersistent) {\n            deleteFile(file);\n            deleteFile(indexFile);\n        }\n    }\n\n    /**\n     * Deletes the data file for this factory.\n     */\n    protected void delete() {\n        deleteFile(file);\n        allocator.clear();\n    }\n\n    /**\n     * Schedule to given task on the disk writer executor service.\n     *\n     * @param <U> return type of the callable\n     * @param call callable to call\n     * @return Future representing the return of this call\n     */\n    protected <U> Future<U> schedule(Callable<U> call) {\n        return diskWriter.submit(call);\n    }\n\n    /**\n     * Read the data at the given marker, and return the associated deserialized Element.\n     *\n     * @param marker marker to read\n     * @return deserialized Element\n     * @throws java.io.IOException on read error\n     * @throws ClassNotFoundException on deserialization error\n     */\n    protected Element read(DiskMarker marker) throws IOException, ClassNotFoundException {\n        final byte[] buffer = new byte[marker.getSize()];\n        final RandomAccessFile data = getDataAccess(marker.getKey());\n        synchronized (data) {\n            // Load the element\n            data.seek(marker.getPosition());\n            data.readFully(buffer);\n        }\n\n        ObjectInputStream objstr = new PreferredLoaderObjectInputStream(new ByteArrayInputStream(buffer), classLoader);\n\n        try {\n            return (Element) objstr.readObject();\n        } finally {\n            objstr.close();\n        }\n    }\n\n    /**\n     * Write the given element to disk, and return the associated marker.\n     *\n     * @param element to write\n     * @return marker representing the element\n     * @throws java.io.IOException on write error\n     */\n    protected DiskMarker write(Element element) throws IOException {\n        MemoryEfficientByteArrayOutputStream buffer = serializeElement(element);\n        int bufferLength = buffer.size();\n        elementSize = bufferLength;\n        DiskMarker marker = alloc(element, bufferLength);\n        // Write the record\n        final RandomAccessFile data = getDataAccess(element.getObjectKey());\n        synchronized (data) {\n            data.seek(marker.getPosition());\n            data.write(buffer.toByteArray(), 0, bufferLength);\n        }\n        return marker;\n    }\n\n    private MemoryEfficientByteArrayOutputStream serializeElement(Element element) throws IOException {\n        // A ConcurrentModificationException can occur because Java's serialization\n        // mechanism is not threadsafe and POJOs are seldom implemented in a threadsafe way.\n        // e.g. we are serializing an ArrayList field while another thread somewhere in the application is appending to it.\n        try {\n            return MemoryEfficientByteArrayOutputStream.serialize(element);\n        } catch (ConcurrentModificationException e) {\n            throw new CacheException(\"Failed to serialize element due to ConcurrentModificationException. \" +\n                                     \"This is frequently the result of inappropriately sharing thread unsafe object \" +\n                                     \"(eg. ArrayList, HashMap, etc) between threads\", e);\n        }\n    }\n\n    private DiskMarker alloc(Element element, int size) throws IOException {\n        //check for a matching chunk\n        Region r = allocator.alloc(size);\n        return createMarker(r.start(), size, element);\n    }\n\n    /**\n     * Free the given marker to be used by a subsequent write.\n     *\n     * @param marker marker to be free'd\n     */\n    protected void free(DiskMarker marker) {\n        allocator.free(new Region(marker.getPosition(), marker.getPosition() + marker.getSize() - 1));\n    }\n\n    /**\n     * Return {@code true} if the disk write queue is full.\n     *\n     * @return {@code true} if the disk write queue is full.\n     */\n    public boolean bufferFull() {\n        return (diskQueue.size() * elementSize) > queueCapacity;\n    }\n\n    /**\n     * Return a reference to the data file backing this factory.\n     *\n     * @return a reference to the data file backing this factory.\n     */\n    public File getDataFile() {\n        return file;\n    }\n\n    /**\n     * DiskWriteTasks are used to serialize elements\n     * to disk and fault in the resultant DiskMarker\n     * instance.\n     */\n    abstract class DiskWriteTask implements Callable<DiskMarker> {\n\n        private final Placeholder placeholder;\n\n        /**\n         * Create a disk-write task for the given placeholder.\n         *\n         * @param p a disk-write task for the given placeholder.\n         */\n        DiskWriteTask(Placeholder p) {\n            this.placeholder = p;\n        }\n\n        /**\n         * Return the placeholder that this task will write.\n         *\n         * @return the placeholder that this task will write.\n         */\n        Placeholder getPlaceholder() {\n            return placeholder;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public DiskMarker call() {\n            try {\n                if (store.containsKey(placeholder.getKey())) {\n                    DiskMarker marker = write(placeholder.getElement());\n                    if (marker != null && store.fault(placeholder.getKey(), placeholder, marker)) {\n                        return marker;\n                    } else {\n                        return null;\n                    }\n                } else {\n                    return null;\n                }\n            } catch (Throwable e) {\n                // TODO Need to clean this up once FrontEndCacheTier is going away completely\n                LOG.error(\"Disk Write of \" + placeholder.getKey() + \" failed: \", e);\n                store.evict(placeholder.getKey(), placeholder);\n                return null;\n            }\n        }\n    }\n\n    /**\n     * Disk free tasks are used to asynchronously free DiskMarker instances under the correct\n     * exclusive write lock.  This ensure markers are not free'd until no more readers can be\n     * holding references to them.\n     */\n    private final class DiskFreeTask implements Callable<Void> {\n        private final Lock lock;\n        private final DiskMarker marker;\n\n        private DiskFreeTask(Lock lock, DiskMarker marker) {\n            this.lock = lock;\n            this.marker = marker;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public Void call() {\n            lock.lock();\n            try {\n                DiskStorageFactory.this.free(marker);\n            } finally {\n                lock.unlock();\n            }\n            return null;\n        }\n    }\n\n    /**\n     * Abstract superclass for all disk substitutes.\n     */\n    public abstract static class DiskSubstitute {\n\n\n        /**\n         * Cached size of this mapping on the Java heap.\n         */\n        protected transient volatile long onHeapSize;\n\n        @IgnoreSizeOf\n        private transient volatile DiskStorageFactory factory;\n\n        /**\n         * Create a disk substitute bound to no factory.  This constructor is used during\n         * de-serialization.\n         */\n        public DiskSubstitute() {\n            this.factory = null;\n        }\n\n        /**\n         * Create a disk substitute bound to the given factory.\n         *\n         * @param factory the factory to bind to.\n         */\n        DiskSubstitute(DiskStorageFactory factory) {\n            this.factory = factory;\n        }\n\n        /**\n         * Return the key to which this marker is (or should be) mapped.\n         *\n         * @return the key to which this marker is (or should be) mapped.\n         */\n        abstract Object getKey();\n\n        /**\n         * Return the total number of hits on this marker\n         *\n         * @return the total number of hits on this marker\n         */\n        abstract long getHitCount();\n\n        /**\n         * Return the time at which this marker expires.\n         *\n         * @return the time at which this marker expires.\n         */\n        abstract long getExpirationTime();\n\n        /**\n         * Mark the disk substitute as installed\n         */\n        abstract void installed();\n\n        /**\n         * Returns the {@link DiskStorageFactory} instance that generated this <code>DiskSubstitute</code>\n         *\n         * @return an <code>ElementProxyFactory</code>\n         */\n        public final DiskStorageFactory getFactory() {\n            return factory;\n        }\n\n        /**\n         * Bind this marker to a given factory.\n         * <p>\n         * Used during deserialization of markers to associate them with the deserializing factory.\n         * @param factory the factory to bind to\n         */\n        void bindFactory(DiskStorageFactory factory) {\n            this.factory = factory;\n        }\n    }\n\n    /**\n     * Placeholder instances are put in place to prevent\n     * duplicate write requests while Elements are being\n     * written to disk.\n     */\n    final class Placeholder extends DiskSubstitute {\n        @IgnoreSizeOf\n        private final Object key;\n        private final Element element;\n\n        private volatile boolean failedToFlush;\n\n        /**\n         * Create a Placeholder wrapping the given element and key.\n         *\n         * @param element the element to wrap\n         */\n        Placeholder(Element element) {\n            super(DiskStorageFactory.this);\n            this.key = element.getObjectKey();\n            this.element = element;\n        }\n\n        /**\n         * Whether flushing this to disk ever failed\n         * @return true if failed, otherwise false\n         */\n        boolean hasFailedToFlush() {\n            return failedToFlush;\n        }\n\n        private void setFailedToFlush(final boolean failedToFlush) {\n            this.failedToFlush = failedToFlush;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public void installed() {\n            DiskStorageFactory.this.schedule(new PersistentDiskWriteTask(this));\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        Object getKey() {\n            return key;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        long getHitCount() {\n            return getElement().getHitCount();\n        }\n\n        @Override\n        long getExpirationTime() {\n            return getElement().getExpirationTime();\n        }\n\n        /**\n         * Return the element that this Placeholder is wrapping.\n         * @return the element that this Placeholder is wrapping.\n         */\n        Element getElement() {\n            return element;\n        }\n    }\n\n    /**\n     * DiskMarker instances point to the location of their\n     * associated serialized Element instance.\n     */\n    public static class DiskMarker extends DiskSubstitute implements Serializable {\n\n        @IgnoreSizeOf\n        private final Object key;\n\n        private final long position;\n        private final int size;\n\n        private volatile long hitCount;\n\n        private volatile long expiry;\n\n        /**\n         * Create a new marker tied to the given factory instance.\n         *\n         * @param factory factory responsible for this marker\n         * @param position position on disk where the element will be stored\n         * @param size size of the serialized element\n         * @param element element being stored\n         */\n        DiskMarker(DiskStorageFactory factory, long position, int size, Element element) {\n            super(factory);\n            this.position = position;\n            this.size = size;\n\n            this.key = element.getObjectKey();\n            this.hitCount = element.getHitCount();\n            this.expiry = TimeUtil.toMillis(TimeUtil.toSecs(element.getExpirationTime()));\n        }\n\n        /**\n         * Create a new marker tied to the given factory instance.\n         *\n         * @param factory factory responsible for this marker\n         * @param position position on disk where the element will be stored\n         * @param size size of the serialized element\n         * @param key key to which this element is mapped\n         * @param hits hit count for this element\n         */\n        DiskMarker(DiskStorageFactory factory, long position, int size, Object key, long hits) {\n            super(factory);\n            this.position = position;\n            this.size = size;\n\n            this.key = key;\n            this.hitCount = hits;\n        }\n\n        /**\n         * Key to which this Element is mapped.\n         *\n         * @return key for this Element\n         */\n        @Override\n        Object getKey() {\n            return key;\n        }\n\n        /**\n         * Number of hits on this Element.\n         */\n        @Override\n        long getHitCount() {\n            return hitCount;\n        }\n\n        /**\n         * Disk offset at which this element is stored.\n         *\n         * @return disk offset\n         */\n        private long getPosition() {\n            return position;\n        }\n\n        /**\n         * Returns the size of the currently occupying element.\n         *\n         * @return size of the stored element\n         */\n        public int getSize() {\n            return size;\n        }\n\n        /**\n         * {@inheritDoc}\n         * <p>\n         * A No-Op\n         */\n        @Override\n        public void installed() {\n            //no-op\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public long getExpirationTime() {\n            return expiry;\n        }\n\n        /**\n         * Increment statistic associated with a hit on this cache.\n         *\n         * @param e element deserialized from disk\n         */\n        void hit(Element e) {\n            hitCount++;\n            expiry = e.getExpirationTime();\n        }\n\n        /**\n         * Updates the stats from memory\n         * @param e\n         */\n        void updateStats(Element e) {\n            hitCount = e.getHitCount();\n            expiry = e.getExpirationTime();\n        }\n    }\n\n\n    /**\n     * Remove elements created by this factory if they have expired.\n     */\n    public void expireElements() {\n        new DiskExpiryTask().run();\n    }\n\n    /**\n     * Causes removal of all expired elements (and fires the relevant events).\n     */\n    private final class DiskExpiryTask implements Runnable {\n\n        /**\n         * {@inheritDoc}\n         */\n        public void run() {\n            long now = System.currentTimeMillis();\n            for (Object key : store.keySet()) {\n                Object value = store.unretrievedGet(key);\n                if (created(value) && value instanceof DiskStorageFactory.DiskMarker) {\n                    checkExpiry((DiskMarker) value, now);\n                }\n            }\n        }\n\n        private void checkExpiry(DiskMarker marker, long now) {\n            if (marker.getExpirationTime() < now) {\n                store.evict(marker.getKey(), marker);\n            }\n        }\n    }\n\n    /**\n     * Attempt to delete the corresponding file and log an error on failure.\n     * @param f the file to delete\n     */\n    protected static void deleteFile(File f) {\n        if (!f.delete()) {\n            LOG.debug(\"Failed to delete file {}\", f.getName());\n        }\n    }\n\n\n    /**\n     * Create a disk substitute for an element\n     *\n     * @param element the element to create a disk substitute for\n     * @return The substitute element\n     * @throws IllegalArgumentException if element cannot be substituted\n     */\n    public DiskSubstitute create(Element element) throws IllegalArgumentException {\n        return new Placeholder(element);\n    }\n\n    /**\n     * Decodes the supplied {@link DiskSubstitute}.\n     *\n     * @param object ElementSubstitute to decode\n     * @return the decoded element\n     */\n    public Element retrieve(DiskSubstitute object) {\n        if (object instanceof DiskMarker) {\n            try {\n                DiskMarker marker = (DiskMarker) object;\n                return read(marker);\n            } catch (IOException e) {\n                throw new CacheException(e);\n            } catch (ClassNotFoundException e) {\n                throw new CacheException(e);\n            }\n        } else if (object instanceof Placeholder) {\n            return ((Placeholder) object).getElement();\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Decodes the supplied {@link DiskSubstitute}, updating statistics.\n     *\n     * @param object ElementSubstitute to decode\n     * @return the decoded element\n     */\n    public Element retrieve(DiskSubstitute object, Segment segment) {\n        if (object instanceof DiskMarker) {\n            try {\n                DiskMarker marker = (DiskMarker) object;\n                Element e = read(marker);\n                marker.hit(e);\n                return e;\n            } catch (IOException e) {\n                throw new CacheException(e);\n            } catch (ClassNotFoundException e) {\n                throw new CacheException(e);\n            }\n        } else if (object instanceof DiskStorageFactory.Placeholder) {\n            return ((Placeholder) object).getElement();\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Returns <code>true</code> if this factory created the given object.\n     *\n     * @param object object to check\n     * @return <code>true</code> if object created by this factory\n     */\n    public boolean created(Object object) {\n        if (object instanceof DiskSubstitute) {\n            return ((DiskSubstitute) object).getFactory() == this;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Unbinds a store instance from this factory\n     */\n    public void unbind() {\n        try {\n            flushTask.call();\n        } catch (Throwable t) {\n            LOG.error(\"Could not flush disk cache. Initial cause was \" + t.getMessage(), t);\n        }\n\n        try {\n            shutdown();\n            if (diskStorePathManager.isAutoCreated()) {\n                deleteFile(indexFile);\n                delete();\n            }\n        } catch (IOException e) {\n            LOG.error(\"Could not shut down disk cache. Initial cause was \" + e.getMessage(), e);\n        }\n    }\n\n    /**\n     * Schedule a flush (index write) for this factory.\n     * @return a Future\n     */\n    public Future<Void> flush() {\n        return schedule(flushTask);\n    }\n\n    private DiskMarker createMarker(long position, int size, Element element) {\n        return new DiskMarker(this, position, size, element);\n    }\n\n    private boolean isPinningEnabled() {\n        return pinningEnabled;\n    }\n\n    /**\n     * Evict some elements, if possible\n     *\n     * @param count the number of elements to evict\n     * @return the number of elements actually evicted\n     */\n    int evict(int count) {\n        // see void onDiskEvict(int size, Object keyHint)\n        if (isPinningEnabled()) {\n            return 0;\n        }\n\n        int evicted = 0;\n        for (int i = 0; i < count; i++) {\n            DiskSubstitute target = this.getDiskEvictionTarget(null, count);\n            if (target != null) {\n                Element evictedElement = store.evictElement(target.getKey(), null);\n                if (evictedElement != null) {\n                    evicted++;\n                }\n            }\n        }\n        return evicted;\n    }\n\n    /**\n     * Filters for on-disk elements created by this factory\n     */\n    private class OnDiskFilter implements ElementSubstituteFilter {\n\n        /**\n         * {@inheritDoc}\n         */\n        public boolean allows(Object object) {\n            if (!created(object)) {\n                return false;\n            }\n\n            return object instanceof DiskMarker;\n        }\n    }\n\n    /**\n     * Return the number of on-disk elements\n     *\n     * @return the number of on-disk elements\n     */\n    public int getOnDiskSize() {\n        return onDisk.get();\n    }\n\n    /**\n     * Set the maximum on-disk capacity for this factory.\n     *\n     * @param capacity the maximum on-disk capacity for this factory.\n     */\n    public void setOnDiskCapacity(int capacity) {\n        diskCapacity = capacity;\n    }\n\n    /**\n     * accessor to the on-disk capacity\n     * @return the capacity\n     */\n    int getDiskCapacity() {\n        return diskCapacity == 0 ? Integer.MAX_VALUE : diskCapacity;\n    }\n\n    private void onDiskEvict(int size, Object keyHint) {\n        if (diskCapacity > 0 && !isPinningEnabled()) {\n            int overflow = size - diskCapacity;\n            for (int i = 0; i < Math.min(MAX_EVICT, overflow); i++) {\n                DiskSubstitute target = getDiskEvictionTarget(keyHint, size);\n                if (target != null) {\n                    final Element element = store.evictElement(target.getKey(), target);\n                    if (element != null && onDisk.get() <= diskCapacity) {\n                        break;\n                    }\n                }\n            }\n        }\n    }\n\n    private DiskSubstitute getDiskEvictionTarget(Object keyHint, int size) {\n        List<DiskSubstitute> sample = store.getRandomSample(onDiskFilter, Math.min(SAMPLE_SIZE, size), keyHint);\n        DiskSubstitute target = null;\n        DiskSubstitute hintTarget = null;\n        for (DiskSubstitute substitute : sample) {\n            if ((target == null) || (substitute.getHitCount() < target.getHitCount())) {\n                if (substitute.getKey().equals(keyHint)) {\n                    hintTarget = substitute;\n                } else {\n                    target = substitute;\n                }\n            }\n        }\n        return target != null ? target : hintTarget;\n    }\n\n    /**\n     * Disk write task implementation for disk persistent stores.\n     */\n    private final class PersistentDiskWriteTask extends DiskWriteTask {\n\n        /**\n         * Create a disk persistent disk-write task for this placeholder.\n         *\n         * @param p the placeholder\n         */\n        PersistentDiskWriteTask(Placeholder p) {\n            super(p);\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        @Override\n        public DiskMarker call() {\n            DiskMarker result = super.call();\n            if (result != null) {\n                int disk = onDisk.incrementAndGet();\n                onDiskEvict(disk, getPlaceholder().getKey());\n            }\n            return result;\n        }\n    }\n\n    /**\n     * Task that writes the index file for this factory.\n     */\n    class IndexWriteTask implements Callable<Void> {\n\n        private final File index;\n        private final boolean clearOnFlush;\n\n        /**\n         * Create a disk flush task that writes to the given file.\n         *\n         * @param index the file to write the index to\n         * @param clear clear on flush flag\n         */\n        IndexWriteTask(File index, boolean clear) {\n            this.index = index;\n            this.clearOnFlush = clear;\n        }\n\n        /**\n         * {@inheritDoc}\n         */\n        public synchronized Void call() throws IOException, InterruptedException {\n            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(index));\n            try {\n                for (Object key : store.keySet()) {\n                    Object o = store.unretrievedGet(key);\n                    if (o instanceof Placeholder && !((Placeholder)o).failedToFlush) {\n                        o = new PersistentDiskWriteTask((Placeholder) o).call();\n                        if (o == null) {\n                            o = store.unretrievedGet(key);\n                        }\n                    }\n\n                    if (o instanceof DiskMarker) {\n                        DiskMarker marker = (DiskMarker) o;\n                        oos.writeObject(key);\n                        oos.writeObject(marker);\n                    }\n                }\n            } finally {\n                oos.close();\n            }\n            return null;\n        }\n\n    }\n\n    private void loadIndex() {\n        if (!indexFile.exists()) {\n            return;\n        }\n\n        try {\n            ObjectInputStream ois = new PreferredLoaderObjectInputStream(new FileInputStream(indexFile), classLoader);\n            try {\n                Object key = ois.readObject();\n                Object value = ois.readObject();\n\n                DiskMarker marker = (DiskMarker) value;\n                while (true) {\n                    marker.bindFactory(this);\n                    markUsed(marker);\n                    if (store.putRawIfAbsent(key, marker)) {\n                        onDisk.incrementAndGet();\n                    } else {\n                        // the disk pool is full\n                        return;\n                    }\n                    key = ois.readObject();\n                    marker = (DiskMarker) ois.readObject();\n                }\n            } finally {\n                ois.close();\n            }\n        } catch (EOFException e) {\n            // end of file reached, stop processing\n        } catch (Exception e) {\n            LOG.warn(\"Index file {} is corrupt, deleting and ignoring it : {}\", indexFile, e);\n            e.printStackTrace();\n            store.removeAll();\n            deleteFile(indexFile);\n        } finally {\n            shrinkDataFile();\n        }\n    }\n\n    /**\n     * Return the index file for this store.\n     * @return the index file\n     */\n    public File getIndexFile() {\n        return indexFile;\n    }\n}\n
===================================================================
--- ehcache-core/src/main/java/net/sf/ehcache/store/disk/DiskStorageFactory.java	(revision 9392)
+++ ehcache-core/src/main/java/net/sf/ehcache/store/disk/DiskStorageFactory.java	(revision )
@@ -676,6 +676,8 @@
      */
     public static class DiskMarker extends DiskSubstitute implements Serializable {
 
+        private static final long serialVersionUID = -7543910739976884343L;
+
         @IgnoreSizeOf
         private final Object key;
 
