Package freenet.store.saltedhash

Source Code of freenet.store.saltedhash.ResizablePersistentIntBuffer

package freenet.store.saltedhash;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import freenet.support.Fields;
import freenet.support.Logger;
import freenet.support.Ticker;

/** A large resizable block of int's, which is persisted to disk with a specific policy,
* which is either to write it on shutdown, immediately, or every X millis.
*
* It would be better to do this with ByteBuffer's and an IntBuffer view, unfortunately
* it is not possible to subclass ByteBuffer's! Also, ideally we'd memory map, but there
* is no way to unmap, and it is likely there will never be, so resizing would be very
* messy and expensive.
* @author toad
*/
public class ResizablePersistentIntBuffer {
 
  private final File filename;
  private final RandomAccessFile raf;
  private final FileChannel channel;
  private final boolean isNew;
  private int size;
  /** The buffer. When we resize we write-lock and replace this. */
  private int[] buffer;
  private final ReadWriteLock lock;
  // 5 minutes by default. Disk I/O kills disks, and annoys users, so it's a fair tradeoff.
  // Anything other than -1 risks data loss if the node is shut down uncleanly.
  // But it does not damage the store: We recover from it transparently.
  // Note also that any value other than -1 will trigger a bloom filter rebuild after an unclean shutdown, which arguably is the opposite of what we want... :|
  // FIXME make that configurable.
  public static final int DEFAULT_PERSISTENCE_TIME = 300000;
  // FIXME is static the best way to do this? It seems simplest at least...
  /** -1 = write immediately, 0 = write only on shutdown, +ve = write period in millis */
  private static int globalPersistenceTime = DEFAULT_PERSISTENCE_TIME;
  private Ticker ticker;
  /** Is the buffer dirty? Protected by (this). */
  private boolean dirty;
  /** Is the writer job scheduled? Protected by (this). */
  private boolean scheduled;
  /** Is the writer job running? So we can wait for it to complete on shutdown e.g.
   * Protected by (this). */
  private boolean writing;
  private boolean closed;
 
  public static synchronized void setPersistenceTime(int val) {
    globalPersistenceTime = val;
  }
 
  public static synchronized int getPersistenceTime() {
    return globalPersistenceTime;
  }
 
  /** Create the buffer. Open the file, creating if necessary, read in the data, and set
   * its size.
   * @param f The filename.
   * @param size The expected size in ints (i.e. multiply by four to get bytes).
   * @throws IOException
   */
  public ResizablePersistentIntBuffer(File f, int size) throws IOException {
    this.filename = f;
    isNew = !f.exists();
    this.raf = new RandomAccessFile(f, "rw");
    this.lock = new ReentrantReadWriteLock();
    this.size = size;
    buffer = new int[size];
    long expectedLength = ((long)size)*4;
    long realLength = raf.length();
    if(realLength > expectedLength)
      raf.setLength(expectedLength);
    readBuffer((int)Math.min(size, realLength/4));
    if(realLength < expectedLength)
      raf.setLength(expectedLength);
    channel = raf.getChannel();
  }
 
  /** Should be called during startup to fill in an appropriate default value e.g. if the store
   * is completely new. */
  public void fill(int value) {
    for(int i=0;i<buffer.length;i++)
      buffer[i] = value;
  }

  private void readBuffer(int size) throws IOException {
    raf.seek(0);
    byte[] buf = new byte[32768];
    int read = 0;
    while(read < size) {
      int toRead = Math.min(buf.length, (size - read) * 4);
      raf.readFully(buf, 0, toRead);
      int[] data = Fields.bytesToInts(buf, 0, toRead);
      System.arraycopy(data, 0, buffer, read, data.length);
      read += data.length;
    }
  }
 
  public void start(Ticker ticker) {
    synchronized(this) {
      this.ticker = ticker;
      if(dirty) {
        int persistenceTime = getPersistenceTime();
        Logger.normal(this, "Scheduling write of slot cache "+this+" in "+persistenceTime);
        ticker.queueTimedJob(writer, persistenceTime);
        scheduled = true;
      }
    }
  }

  public int get(int offset) {
    lock.readLock().lock();
    if(closed) throw new IllegalStateException("Already shut down");
    try {
      return buffer[offset];
    } finally {
      lock.readLock().unlock();
    }
  }
 
  public void put(int offset, int value) throws IOException {
    put(offset, value, false);
  }

  public void put(int offset, int value, boolean noWrite) throws IOException {
    lock.readLock().lock(); // Only resize needs write lock because it creates a new buffer.
    if(closed) throw new IllegalStateException("Already shut down");
    try {
      int persistenceTime = getPersistenceTime();
      buffer[offset] = value;
      if(persistenceTime == -1 && !noWrite) {
        channel.write(ByteBuffer.wrap(Fields.intToBytes(value)), ((long)offset)*4);
      } else if(persistenceTime > 0) {
        synchronized(this) {
          dirty = true;
          if(ticker != null) {
            if(!scheduled) {
              Logger.normal(this, "Scheduling write of slot cache "+this+" in "+persistenceTime);
              ticker.queueTimedJob(writer, persistenceTime);
              scheduled = true;
            }
          } else {
            Logger.normal(this, "Will scheduling write of slot cache after startup: "+this+" in "+persistenceTime);
          }
        }
      } else {
        synchronized(this) {
          dirty = true;
        }
      }
    } finally {
      lock.readLock().unlock();
    }
  }
 
  private Runnable writer = new Runnable() {

    public void run() {
      Logger.normal(this, "Writing slot cache "+ResizablePersistentIntBuffer.this);
      lock.readLock().lock(); // Protect buffer.
      try {
        synchronized(ResizablePersistentIntBuffer.this) {
          if(writing || !dirty || closed) {
            scheduled = false;
            return;
          }
          scheduled = false;
          dirty = false;
          writing = true;
        }
        try {
          writeBuffer();
        } catch (IOException e) {
          Logger.error(this, "Write failed during shutdown: "+e+" on "+filename, e);
        }
      } finally {
        synchronized(ResizablePersistentIntBuffer.this) {
          writing = false;
          ResizablePersistentIntBuffer.this.notifyAll();
        }
        lock.readLock().unlock();
      }
      Logger.normal(this, "Written slot cache "+ResizablePersistentIntBuffer.this);
    }
   
  };
 
  public void shutdown() {
    lock.writeLock().lock();
    try {
      synchronized(this) {
        if(closed) return;
        closed = true;
        if(writing) {
          // Wait for write to finish.
          while(writing) {
            try {
              wait();
            } catch (InterruptedException e) {
              // Ignore.
            }
          }
          if(!dirty) return;
        }
        writing = true;
      }
      try {
        Logger.normal(this, "Writing slot cache on shutdown: "+this);
        writeBuffer();
      } catch (IOException e) {
        Logger.error(this, "Write failed during shutdown: "+e+" on "+filename, e);
      }
      synchronized(this) {
        writing = false;
      }
      try {
        raf.close();
      } catch (IOException e) {
        Logger.error(this, "Close failed during shutdown: "+e+" on "+filename, e);
      }
    } finally {
      lock.writeLock().unlock();
    }
   
  }
 
  public void abort() {
    lock.writeLock().lock();
    try {
      synchronized(this) {
        if(closed) return;
        closed = true;
      }
      try {
        raf.close();
      } catch (IOException e) {
        Logger.error(this, "Close failed during shutdown: "+e+" on "+filename, e);
      }
    } finally {
      lock.writeLock().unlock();
    }
  }

  private void writeBuffer() throws IOException {
    // FIXME do we need to do partial writes?
    raf.seek(0);
    int written = 0;
    while(written < size) {
      int toWrite = Math.min(32768, size - written);
      byte[] buf = Fields.intsToBytes(buffer, written, toWrite);
      raf.write(buf);
      written += toWrite;
    }
  }
 
  public void resize(int size) {
    lock.writeLock().lock();
    try {
      if(this.size == size) return;
      Logger.normal(this, "Resizing cache from "+this.size+" slots to "+size);
      this.size = size;
      buffer = Arrays.copyOf(buffer, size);
      try {
        raf.setLength(size * 4);
        writeBuffer();
      } catch (IOException e) {
        Logger.error(this, "Failed to change size or write during resize on "+filename+" : "+e, e);
      }
    } finally {
      lock.writeLock().unlock();
    }
  }

  public void forceWrite() {
    Logger.normal(this, "Force write slot cache: "+this);
    lock.readLock().lock();
    try {
      synchronized(this) {
        if(closed) return;
        dirty = false;
        if(writing) {
          // Wait for write to finish.
          while(writing) {
            try {
              wait();
            } catch (InterruptedException e) {
              // Ignore.
            }
          }
          if(!dirty) return;
        }
        writing = true;
      }
      try {
        writeBuffer();
      } catch (IOException e) {
        Logger.error(this, "Write failed during shutdown: "+e+" on "+filename, e);
      }
    } finally {
      synchronized(this) {
        writing = false;
      }
      lock.readLock().unlock();
    }
  }

  public boolean isNew() {
    return isNew;
  }
 
  public String toString() {
    return filename.getPath();
  }

  // Testing only! Hence no lock.
  public void replaceAllEntries(int key, int value) {
    for(int i=0;i<buffer.length;i++)
      if(buffer[i] == key) buffer[i] = value;
  }
 
  public int size() {
    return size;
  }
 
}
TOP

Related Classes of freenet.store.saltedhash.ResizablePersistentIntBuffer

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.