/** 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 Feb 15, 2007 */ package com.bigdata.journal; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import com.bigdata.counters.CounterSet; import com.bigdata.io.DirectBufferPool; import com.bigdata.mdi.AbstractResourceMetadata; import com.bigdata.mdi.IResourceMetadata; import com.bigdata.quorum.Quorum; import com.bigdata.rawstore.AbstractRawWormStore; import com.bigdata.rawstore.IMRMW; import com.bigdata.rawstore.WormAddressManager; import com.bigdata.relation.locator.ILocatableResource; /** * A non-restart-safe store for temporary data that buffers data in memory until * the write cache overflows (or is flushed to the disk) and then converts to a * disk-based store. The backing file (if any) is released when the temporary * store is {@link #close()}d. * * @see BufferMode#Temporary * @see DiskOnlyStrategy * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public class TemporaryRawStore extends AbstractRawWormStore implements IMRMW { private static final Logger log = Logger.getLogger(TemporaryRawStore.class); /** * Note: various things must be synchronized on {@link #buf} in order to * serialize reads, writes, etc. This is because it is {@link #buf} on which * the {@link DiskOnlyStrategy} itself is synchronized. For this reason it * is also a good idea to never allow {@link #buf} to become * <code>null</code>, hence it is declared <code>final</code> here. */ private final DiskOnlyStrategy buf; // /** // * When non-<code>null</code> this is a direct {@link ByteBuffer} // * allocated using the {@link DirectBufferPool} during // * {@link #overflowToDisk()} and handed off to the {@link DiskOnlyStrategy} // * for use as its write cache. When non-<code>null</code> this buffer is // * {@link DirectBufferPool#release(ByteBuffer)}ed back to the // * {@link DirectBufferPool} in {@link #finalize()} to avoid a native memory // * leak. // */ // private ByteBuffer writeCache = null; /** * Store identifier. */ private final UUID uuid = UUID.randomUUID(); /** * Note: this timestamp is NOT generated by a centralized time server. */ private final long createTime; /** * The #of open {@link TemporaryRawStore}s (JVM wide). This is package * private. It is used to chase down unit tests which are not closing() the * store. */ final static AtomicInteger nopen = new AtomicInteger(); /** * The #of closed {@link TemporaryRawStore}s (JVM wide). This is package * private. It is used to chase down unit tests which are not * {@link #close() closing} the store. */ final static AtomicInteger nclose = new AtomicInteger(); /** * Return an empty {@link File} created using the temporary file name * mechanism. The file name will begin with <code>bigdata</code> and end * with <code>.tmp</code>. The file is marked for eventual deletion. */ static protected File getTempFile() { try { final File file = File.createTempFile("bigdata", ".tmp"); file.deleteOnExit(); return file; } catch (IOException ex) { throw new RuntimeException(ex); } } /** * Return an empty {@link File} created using the temporary file name * mechanism in the specified directory. The directory will be created if it * does not exist and the caller has sufficient permissions. The file name * will begin with <code>bigdata</code> and end with <code>.tmp</code>. The * file is marked for eventual deletion. * * @param tmpDir * The directory within which to create the temporary file. */ static public File getTempFile(final File tmpDir) { try { if (!tmpDir.exists()) { if (!tmpDir.mkdirs()) { /* * The return code is ignored. The directory could have been * concurrently created so there is no reason to look at the * return code here. If we can't create the file below then * we have a problem and it will get reported. */ } } final File file = File.createTempFile("bigdata", ".tmp", tmpDir); file.deleteOnExit(); return file; } catch (IOException ex) { throw new RuntimeException(ex); } } /** * The UUID of this {@link TemporaryRawStore}. This is reported as part of * {@link #getResourceMetadata()} and may also be used to ensure that * {@link ILocatableResource}s created on a {@link TemporaryStore} are * placed within a unique namespace. */ @Override final public UUID getUUID() { return uuid; } /** * Create a {@link TemporaryRawStore}. */ public TemporaryRawStore() { this(WormAddressManager.SCALE_UP_OFFSET_BITS); } /** * Create a {@link TemporaryRawStore}. * * @param offsetBits * This determines the capacity of the store file and the maximum * length of a record. The value is passed through to * {@link WormAddressManager#WormAddressManager(int)}. */ public TemporaryRawStore(final int offsetBits) { this(0L/* maximumExtent */, offsetBits, getTempFile()); } /** * Create a {@link TemporaryRawStore} with the specified configuration. * * @param maximumExtent * The maximum extent allowed for the {@link TemporaryRawStore} * -or- ZERO (0L) iff no limit should be imposed. * * @param offsetBits * This determines the capacity of the store file and the maximum * length of a record. The value is passed through to * {@link WormAddressManager#WormAddressManager(int)}. * * @param file * The name of the backing file. The file will be created on * demand if it does not exist. It will be an error if the file * exists and has a non-zero size at that time. The file will be * registered with the JVM for for "delete on exit" and will be * deleted regardless as soon as the store is * {@link #close() closed}. */ public TemporaryRawStore(final long maximumExtent, final int offsetBits, final File file) { super(offsetBits); if(log.isInfoEnabled()) { log.info("offsetBits=" + offsetBits + ", file=" + file // ,new RuntimeException() ); } // Note: timestamp is NOT assigned by a centralized service! this.createTime = System.currentTimeMillis(); // try { // // /* // * Try and acquire a direct buffer to serve as the initial in-memory // * extent and the write cache. // */ // // Note: the timeout here is not such a good idea. It could // // be triggered by a GC pause with the resulting temp store // // then lacking a write cache. // this.writeCache = DirectBufferPool.INSTANCE.acquire(2000L, // TimeUnit.MILLISECONDS); // // } catch (InterruptedException e) { // // throw new RuntimeException(e); // // } catch (TimeoutException e) { // // throw new RuntimeException(e); // // } /* * Note: The initial on disk capacity is exactly the capacity of the * write cache. This implies that the file will be extended 32M at * a time (the default when the initialExtent is less than 32M). */ final long initialExtent = DirectBufferPool.INSTANCE.getBufferCapacity(); /* * Note: This is the overflow trigger point. Since this class does not * support overflow, this value is essentially ignored. However, it must * be GTE [initialExtent] or an exception will be thrown. */ final long overflowExtent = initialExtent; final FileMetadata md = new FileMetadata(// file,// BufferMode.Temporary,// false,// useDirectBuffers (ignored for disk-based modes) initialExtent, // The initial on disk capacity. overflowExtent, // Note: same as [initialExtent] (overflow trigger) true,// create (ignored for temporary files). true,// isEmptyFile (file is either empty or does not exist) true, // deleteOnExit false, // readOnly ForceEnum.No, // forceWrites offsetBits,// true, // writeCacheEnabled 3, // writeCacheBufferCount false, // validateChecksum (desperation option for restart). createTime,// Quorum.NO_QUORUM,// Temporary stores are not HA. false, // alternateRootBlock, null // properties ); buf = new DiskOnlyStrategy(maximumExtent, // // Long.valueOf(Options.DEFAULT_MINIMUM_EXTENSION), md); nopen.incrementAndGet(); } /** * Closes the store if it gets GCd. */ @Override protected void finalize() throws Throwable { try { synchronized (buf) { if (buf.isOpen()) { close(); if (log.isInfoEnabled()) log.info("Finalized temp store"); } } } catch (Throwable t) { log.error("Ignoring: " + t, t); } super.finalize(); } @Override public String toString() { return getClass().getName() + "{file=" + getFile() + "}"; } @Override final public File getFile() { return buf.getFile(); } /** * Close the store and delete the associated file, if any. */ @Override public void close() { synchronized (buf) { if (log.isInfoEnabled()) log.info("Closing temp store"); try { if (!buf.isOpen()) throw new IllegalStateException(); buf.destroy(); nclose.incrementAndGet(); } finally { // if (writeCache != null) { // // try { // // DirectBufferPool.INSTANCE.release(writeCache); // // writeCache = null; // // } catch (Throwable t) { // // log.error(t, t); // // } // // } // @see BLZG-1501 (remove LRUNexus) // if (LRUNexus.INSTANCE != null) { // // try { // // LRUNexus.INSTANCE.deleteCache(getUUID()); // // } catch (Throwable t) { // // log.error(t, t); // // } // // } } } } /** * Note: This operation is a NOP since {@link #close()} always deletes the * backing file and {@link #deleteResources()} requires that the store is * closed as a pre-condition. */ @Override public void deleteResources() { synchronized (buf) { if (buf.isOpen()) { throw new IllegalStateException(); } /* * NOP */ } } /** * Note: Temporary stores do not have persistent resource descriptions. */ @Override final public IResourceMetadata getResourceMetadata() { final File file = buf.getFile(); final String fileStr = file == null ? "" : file.toString(); return new ResourceMetadata(this, fileStr); } /** * Static class since must be {@link Serializable}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ static final class ResourceMetadata extends AbstractResourceMetadata { /** * De-serializator ctor. */ public ResourceMetadata() { } public ResourceMetadata(final TemporaryRawStore store, final String fileStr) { super(fileStr, // store.buf.getExtent() store.uuid,// store.createTime, // 0L// commitTime ); } private static final long serialVersionUID = 1L; @Override public boolean isJournal() { return false; } @Override public boolean isIndexSegment() { return false; } } final public DiskOnlyStrategy getBufferStrategy() { return buf; } /** * Simply delegates to {@link #close()} since {@link #close()} always * deletes the backing file for a temporary store. */ @Override final public void destroy() { if(isOpen()) close(); } @Override final public void force(boolean metadata) { buf.force(metadata); } @Override final public long size() { return buf.size(); } final protected void assertOpen() { if (!isOpen()) throw new IllegalStateException(); } @Override final public boolean isOpen() { return buf.isOpen(); } @Override final public boolean isReadOnly() { return buf.isReadOnly(); } /** * Always returns <code>false</code> since the store will be deleted as soon * as it is closed. */ @Override final public boolean isStable() { if (!isOpen()) throw new IllegalStateException(); return false; } /** * Return <code>false</code> since the temporary store is (at least in * principle) backed by disk. */ @Override final public boolean isFullyBuffered() { if (!isOpen()) throw new IllegalStateException(); return false; } @Override final public ByteBuffer read(long addr) { return buf.read(addr); } @Override final public long write(ByteBuffer data) { return buf.write(data); } // final public long allocate(int nbytes) { // // return buf.allocate(nbytes); // // } // // final public void update(long addr, int off, ByteBuffer data) { // // buf.update(addr, off, data); // // } /** * The maximum length of a record that may be written on the store. */ final public int getMaxRecordSize() { return ((AbstractRawWormStore) buf).getAddressManager() .getMaxByteCount(); } @Override public CounterSet getCounters() { return buf.getCounters(); } @Override public void delete(long addr) { // void } }