Package freenet.store

Source Code of freenet.store.SlashdotStore$DiskBlock

package freenet.store;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import freenet.keys.KeyVerifyException;
import freenet.node.stats.StoreAccessStats;
import freenet.node.useralerts.UserAlertManager;
import freenet.support.ByteArrayWrapper;
import freenet.support.LRUMap;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Ticker;
import freenet.support.Logger.LogLevel;
import freenet.support.api.Bucket;
import freenet.support.io.TempBucketFactory;

/** Short-term cache. Used to cache all blocks retrieved in the last 30 minutes (on low
* security levels), or just to cache data fetched through ULPRs (on higher security levels).
* - Strict LRU.
* - Size limit.
* - Strictly enforced time limit.
* - Blocks are encrypted, and kept in temp files.
*
* @author Matthew Toseland <toad@amphibian.dyndns.org> (0xE43DA450)
*/
public class SlashdotStore<T extends StorableBlock> implements FreenetStore<T> {

  private static volatile boolean logDEBUG;

  static {
    Logger.registerLogThresholdCallback(new LogThresholdCallback(){
      @Override
      public void shouldUpdate(){
        logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
      }
    });
  }
 
  private class DiskBlock {
    Bucket data;
    long lastAccessed;
  }
 
  private final TempBucketFactory bf;
 
  private long maxLifetime;
 
  private final long purgePeriod;
 
  // PURGING OLD DATA:
  // Every X period? I don't think it matters if we're a few minutes out, and it's probably easiest that way...
 
  private final Ticker ticker;
 
  private final LRUMap<ByteArrayWrapper, DiskBlock> blocksByRoutingKey;
 
  private final StoreCallback<T> callback;
 
  private int maxKeys;
 
  private long hits;
  private long misses;
  private long writes;
 
  private final int headerSize;
  private final int dataSize;
  private final int fullKeySize;
 
  public SlashdotStore(StoreCallback<T> callback, int maxKeys, long maxLifetime, long purgePeriod, Ticker ticker, TempBucketFactory tbf) {
    this.callback = callback;
    this.blocksByRoutingKey = LRUMap.createSafeMap(ByteArrayWrapper.FAST_COMPARATOR);
    this.maxKeys = maxKeys;
    this.bf = tbf;
    this.ticker = ticker;
    this.maxLifetime = maxLifetime;
    this.purgePeriod = purgePeriod;
    callback.setStore(this);
    this.headerSize = callback.headerLength();
    this.dataSize = callback.dataLength();
    this.fullKeySize = callback.fullKeyLength();
    Runnable purgeOldData = new Runnable() {

      @Override
      public void run() {
        try {
          purgeOldData();
        } finally {
          SlashdotStore.this.ticker.queueTimedJob(this, SlashdotStore.this.purgePeriod);
        }
      }

    };
    ticker.queueTimedJob(purgeOldData, maxLifetime + purgePeriod);
  }
 
  /**
   * @param meta IGNORED!
   */
  @Override
  public T fetch(byte[] routingKey, byte[] fullKey, boolean dontPromote, boolean canReadClientCache, boolean canReadSlashdotCache, boolean ignoreOldBlocks, BlockMetadata meta) throws IOException {
    ByteArrayWrapper key = new ByteArrayWrapper(routingKey);
    DiskBlock block;
    long timeAccessed;
    synchronized(this) {
      block = blocksByRoutingKey.get(key);
      if(block == null) {
        misses++;
        return null;
      }
      timeAccessed = block.lastAccessed;
    }
    InputStream in = block.data.getInputStream();
    DataInputStream dis = new DataInputStream(in);
    byte[] fk = new byte[fullKeySize];
    byte[] header = new byte[headerSize];
    byte[] data = new byte[dataSize];
    dis.readFully(fk);
    dis.readFully(header);
    dis.readFully(data);
    in.close();
    try {
      T ret =
        callback.construct(data, header, routingKey, fk, canReadClientCache, canReadSlashdotCache, null, null);
      synchronized(this) {
        hits++;
        if(!dontPromote) {
          block.lastAccessed = System.currentTimeMillis();
          blocksByRoutingKey.push(key, block);
        }
      }
      if(logDEBUG) Logger.debug(this, "Block was last accessed "+(System.currentTimeMillis() - timeAccessed)+"ms ago");
      return ret;
    } catch (KeyVerifyException e) {
      block.data.free();
      synchronized(this) {
        blocksByRoutingKey.removeKey(key);
        misses++;
      }
      return null;
    }
  }

  @Override
  public long getBloomFalsePositive() {
    return -1;
  }

  @Override
  public long getMaxKeys() {
    return maxKeys;
  }

  @Override
  public long hits() {
    return hits;
  }

  @Override
  public long keyCount() {
    return blocksByRoutingKey.size();
  }

  @Override
  public long misses() {
    return misses;
  }

  @Override
  public boolean probablyInStore(byte[] routingKey) {
    ByteArrayWrapper key = new ByteArrayWrapper(routingKey);
    return blocksByRoutingKey.containsKey(key);
  }

  /**
   * @param isOldBlock Ignored, we don't distinguish between stuff that should be cached and
   * stuff that shouldn't be cached; really it's all in the latter category anyway here!
   */
  @Override
  public void put(T block, byte[] data, byte[] header, boolean overwrite, boolean isOldBlock) throws IOException, KeyCollisionException {
    byte[] routingkey = block.getRoutingKey();
    byte[] fullKey = block.getFullKey();
   
    Bucket bucket = bf.makeBucket(fullKeySize + dataSize + headerSize);
    OutputStream os = bucket.getOutputStream();
    try {
    os.write(fullKey);
    os.write(header);
    os.write(data);
    } finally {
    os.close();
    }
   
    DiskBlock stored = new DiskBlock();
    stored.data = bucket;
    purgeOldData(new ByteArrayWrapper(routingkey), stored);
  }

  @Override
  public void setMaxKeys(long maxStoreKeys, boolean shrinkNow) throws IOException {
    if(maxStoreKeys > Integer.MAX_VALUE) throw new IllegalArgumentException();
    this.maxKeys = (int) maxStoreKeys;
    if(shrinkNow) {
      purgeOldData();
    } else {
      ticker.queueTimedJob(new Runnable() {

        @Override
        public void run() {
          purgeOldData();
          // Don't re-schedule
        }
       
      }, 0);
    }
  }

  @Override
  public long writes() {
    return writes;
  }

  protected void purgeOldData() {
    purgeOldData(null, null);
  }
 
  protected void purgeOldData(ByteArrayWrapper key, DiskBlock addFirst) {
      if(logDEBUG) Logger.minor(this, "Dumping old data from "+this+(addFirst == null ? "" : " and adding "+addFirst.data));
    List<DiskBlock> blocks = null;
    DiskBlock oldBlock;
    synchronized(this) {
      long now = System.currentTimeMillis();
      if(addFirst != null) {
        addFirst.lastAccessed = now;
        oldBlock = blocksByRoutingKey.push(key, addFirst);
        if(oldBlock != null) {
            Logger.warning(this, "Replacing "+oldBlock+" with "+addFirst+" for "+key);
                  if(blocks == null) blocks = new ArrayList<DiskBlock>();
                  if(logDEBUG) Logger.minor(this, "Will dump "+oldBlock);
                  blocks.add(oldBlock);
        }
        writes++;
      }
      while(true) {
        if(blocksByRoutingKey.isEmpty()) break;
        DiskBlock block = blocksByRoutingKey.peekValue();
        if(now - block.lastAccessed < maxLifetime && blocksByRoutingKey.size() < maxKeys) break;
        if(blocks == null) blocks = new ArrayList<DiskBlock>();
        if(logDEBUG) Logger.minor(this, "Will dump "+block);
        blocks.add(block);
        blocksByRoutingKey.popValue();
      }
    }
    if(blocks == null) return;
    for(DiskBlock block : blocks) {
      block.data.free();
    }
  }

  public synchronized Long getLifetime() {
    return maxLifetime;
  }

  public synchronized void setLifetime(Long val) {
    maxLifetime = val;
  }
 
  @Override
  public StoreAccessStats getSessionAccessStats() {
    return new StoreAccessStats() {

      @Override
      public long hits() {
        return hits;
      }

      @Override
      public long misses() {
        return misses;
      }

      @Override
      public long falsePos() {
        return 0;
      }

      @Override
      public long writes() {
        return writes;
      }
     
    };
  }

  @Override
  public StoreAccessStats getTotalAccessStats() {
    return null;
  }

  @Override
  public boolean start(Ticker ticker, boolean longStart) throws IOException {
    return false;
  }

  @Override
  public void setUserAlertManager(UserAlertManager userAlertManager) {
    // Do nothing
  }

  @Override
  public FreenetStore<T> getUnderlyingStore() {
    return this;
  }

  @Override
  public void close() {
    // Do nothing
  }
}
TOP

Related Classes of freenet.store.SlashdotStore$DiskBlock

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.