/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Sep 3, 2008 */ package com.bigdata.journal; import java.io.File; import java.lang.ref.WeakReference; import java.util.Iterator; import java.util.Map; import java.util.Properties; import java.util.UUID; import org.apache.log4j.Logger; import com.bigdata.cache.ConcurrentWeakValueCache; import com.bigdata.rawstore.WormAddressManager; import com.bigdata.util.Bytes; import com.bigdata.util.InnerCause; /** * Helper class for {@link IIndexStore#getTempStore()}. This class is very light * weight. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TemporaryStoreFactory { private static final transient Logger log = Logger .getLogger(TemporaryStoreFactory.class); /** * Configuration options for the {@link TemporaryStoreFactory}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public interface Options { /** * The directory within which the {@link TemporaryStore}s will be * created. The default is whichever directory is specified by * the Java system property <code>java.io.tmpdir</code>. */ String TMP_DIR = TemporaryStoreFactory.class.getName() + ".tmpDir"; /** * The Java system property whose value is the default directory for the * {@link TemporaryStore}s created by this factory ( * <code>java.io.tmpdir</code>). */ String JAVA_TMP_DIR_PROPERTY = "java.io.tmpdir"; /** * The #of bits in a 64-bit long integer identifier that are used to encode * the byte offset of a record in the store as an unsigned integer. The * default is {@link WormAddressManager#SCALE_UP_OFFSET_BITS}. * * @see WormAddressManager#SCALE_UP_OFFSET_BITS * @see WormAddressManager#SCALE_OUT_OFFSET_BITS */ String OFFSET_BITS = TemporaryStoreFactory.class.getName()+".offsetBits"; String DEFAULT_OFFSET_BITS = ""+WormAddressManager.SCALE_UP_OFFSET_BITS; /** * The maximum extent of the existing {@link TemporaryStore} before * {@link Journal#getTempStore()} will return a new * {@link TemporaryStore} instance (default * {@value #DEFAULT_TEMPORARY_STORE_MAX_EXTENT}). * <p> * {@link TemporaryStore}s are reused in order to keep down the #of file * handles and the latency to create a new {@link TemporaryStore} for * operations which make heavy use of temporary data. Old * {@link TemporaryStore} instances will be reclaimed (and deleted on * the disk) once they are no longer strongly referenced. * <p> * This option DOES NOT place an absolute limit on the maximum extent of * a {@link TemporaryStore} instance. A {@link TemporaryStore} will * continue to grow in side as long as a process continues to write on * the {@link TemporaryStore}. */ String MAX_EXTENT = TemporaryStoreFactory.class.getName() + ".maxExtent"; /** * The default maximum extent ({@value #DEFAULT_MAX_EXTENT}). A new * {@link TemporaryStore} will be created by {@link #getTempStore()} * when the extent of the current {@link TemporaryStore} reaches this * value. However, the temporary store will continue to grow as long as * there are execution contexts which retain a reference to that * instance. * <p> * Note: Each file system has its own limits on the maximum size of a * file. FAT16 limits the maximum file size to only 2G. FAT32 supports * 4G files. NTFS and most un*x file systems support 16G+ files. A safe * point for allocating a new temporary store for new requests is * therefore LT the smallest maximum file size supported by any of the * common file systems. * <p> * A temporary store that reaches the maximum size allowed for the file * system will fail when a request is made to extend that file. How that * effects processing depends of course on the purpose to which the * temporary store was being applied. E.g., to buffer a transaction, to * perform truth maintenance, etc. * * @todo If we had more visibility into the file system for a given * logical disk then we could apply that information to * dynamically set the cutover point for the temporary store based * on the backing file system. Unfortunately, {@link File} does * not provide us with that information. */ String DEFAULT_MAX_EXTENT = "" + (1 * Bytes.gigabyte); } /** * The current {@link TemporaryStore}. This is initially <code>null</code>. * If there are no strong references to the current {@link TemporaryStore} * then this reference can be cleared by GC and the store will be * automatically closed and its backing file deleted on the disk. */ private WeakReference<TemporaryStore> ref = null; /** The directory within which the temporary files will be created. */ private final File tmpDir; /** * The offset bits for the {@link TemporaryStore} instances. * * @see WormAddressManager */ private final int offsetBits; /** * The maximum extent of the current {@link TemporaryStore} before a new * instance will be returned by {@link #getTempStore()}. */ private final long maxExtent; /** * Constructor uses the Java system properties to configure the factory. * The {@link Options} may be used to override the defaults if specified * in the environment or on the JVM command line. */ public TemporaryStoreFactory() { this( // new File(System.getProperty(Options.TMP_DIR, System .getProperty(Options.JAVA_TMP_DIR_PROPERTY))), // Integer.valueOf(System.getProperty(Options.OFFSET_BITS, Options.DEFAULT_OFFSET_BITS)), // Long.valueOf(System.getProperty(Options.MAX_EXTENT, Options.DEFAULT_MAX_EXTENT)) // ); } /** * Constructor uses the caller's properties object to configure the factory. * * @param properties * Properties used to configure the factory. */ public TemporaryStoreFactory(final Properties properties) { this( // new File(properties.getProperty(Options.TMP_DIR, System .getProperty(Options.JAVA_TMP_DIR_PROPERTY))), // Integer.valueOf(properties.getProperty(Options.OFFSET_BITS, Options.DEFAULT_OFFSET_BITS)), // Long.valueOf(properties.getProperty(Options.MAX_EXTENT, Options.DEFAULT_MAX_EXTENT)) // ); } /** * Constructor uses the caller's values to configure the factory. * * @param tmpDir * The directory within which the {@link TemporaryStore} files * will be created. * @param offsetBits * This value governs how many records can exist within the * {@link TemporaryStore} and the maximum size of those records. * A good default value is * {@link WormAddressManager#SCALE_UP_OFFSET_BITS}. * @param maxExtent * The maximum extent of the current {@link TemporaryStore} * before {@link #getTempStore()} will return a new * {@link TemporaryStore}. * * @throws IllegalArgumentException * if <i>maxExtent</i> is negative (zero is allowed and will * cause each request to return a distinct * {@link TemporaryStore}). */ public TemporaryStoreFactory(final File tmpDir, final int offsetBits, final long maxExtent) { if (tmpDir == null) throw new IllegalArgumentException(); WormAddressManager.assertOffsetBits(offsetBits); if (maxExtent < 0L) throw new IllegalArgumentException(); this.tmpDir = tmpDir; this.offsetBits = offsetBits; this.maxExtent = maxExtent; if (log.isInfoEnabled()) { log.info(Options.TMP_DIR + "=" + tmpDir); log.info(Options.OFFSET_BITS + "=" + offsetBits); log.info(Options.MAX_EXTENT + "=" + maxExtent); } } /** * Return a {@link TemporaryStore}. If there is no existing * {@link TemporaryStore} then a new instance is returned. If there is an * existing {@link TemporaryStore} and its extent is greater then the * configured maximum extent then a new {@link TemporaryStore} will be * created and returned. Otherwise the existing instance is returned. */ synchronized public TemporaryStore getTempStore() { TemporaryStore t = ref == null ? null : ref.get(); if (t == null || t.getBufferStrategy().getExtent() > maxExtent) { // create an empty backing file in the specified directory. final File file = TemporaryRawStore.getTempFile(tmpDir); // Create a temporary store using that backing file. t = new TemporaryStore(offsetBits, file); // put into the weak value cache. stores.put(t.getUUID(), t); // return weak reference. ref = new WeakReference<TemporaryStore>(t); } return t; } /** * Weak value cache of the open temporary stores. Temporary stores are * automatically cleared from this cache after they have become only weakly * reachable. Temporary stores are closed by a finalizer, and are deleted * when closed. It is possible that a temporary store will not be finalized * if the store is shutdown and that {@link File#deleteOnExit()} will not be * called, in which case you may have dead temporary stores lying around. */ private ConcurrentWeakValueCache<UUID, TemporaryStore> stores = new ConcurrentWeakValueCache<UUID, TemporaryStore>( 0); /** * Close all open temporary stores allocated by this factory. */ synchronized public void closeAll() { boolean interrupted = false; final Iterator<Map.Entry<UUID,WeakReference<TemporaryStore>>> itr = stores.entryIterator(); while(itr.hasNext()) { final Map.Entry<UUID,WeakReference<TemporaryStore>> entry = itr.next(); final TemporaryStore store = entry.getValue().get(); if (store == null) { // The weak reference has been cleared. continue; } // close the temporary store (it will be deleted synchronously). if (store.isOpen()) { try { store.close(); } catch (Throwable t) { if (InnerCause.isInnerCause(t, InterruptedException.class)) interrupted = true; } } } if (interrupted) { // Propagate the interrupt. Thread.currentThread().interrupt(); } } }