Package dovetaildb.dbservice

Source Code of dovetaildb.dbservice.RebuildingDbService$RebuildingThread

package dovetaildb.dbservice;

import java.io.File;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;


import junit.framework.TestCase;

import dovetaildb.api.ApiBuffer;
import dovetaildb.api.AutocommitBuffer;
import dovetaildb.util.Util;

public class RebuildingDbService extends WrappingDbService {

  private static final long serialVersionUID = 88243319438954L;

  transient protected RebuildingThread rebuildingThread;
  transient protected ObjectOutputStream backlog;
  transient protected ObjectInputStream processlog;
  protected DbServiceFactory factory;
  protected DbService nextGeneration;
  protected File homeDir;
  protected File backlogFile;
  protected File processlogFile;
  protected long deadSpaceCount = 0;
  protected long txnSpace = 0;
  protected final long deadSpaceThreshold;
  protected final long txnSpaceIncr = 1000000000;
  protected final long cutover_delay_prevention_millis = 100;


  public String toString() {
    return "RebuildingDbService(factory="+factory+", cur="+subService+", next="+nextGeneration+", rebuild="+rebuildingThread+")";
  }
 
  @Override
  public synchronized long getHighestCompletedTxnId() {
    return subService.getHighestCompletedTxnId();
  }
 
  private static DbService makeWithHome(DbServiceFactory factory, File root, String dirName, DbService prevService) {
    File home = new File(root.getAbsolutePath() + File.separatorChar + dirName);
    if (home.exists()) throw new RuntimeException("DbService path already exists: "+home);
    if (!home.mkdir()) throw new RuntimeException("Could not mkdir home: "+home);
    return factory.makeDbService(home, prevService);
  }

  protected void log(Level level, String message) {
    Util.logger.log(level, "Rebuilder @ "+homeDir+": "+message);
  }
 
  public RebuildingDbService(File homeDir, DbServiceFactory factory, long deadSpaceThreshold) {
    super(makeWithHome(factory, homeDir, Util.genUUID(), null));
    subService = new TxnSpacedDbService(subService, txnSpace, txnSpace+txnSpaceIncr);
    subService.setDirtyListener(this);
    openBacklog();
    openProcesslog();
    this.homeDir = homeDir;
    this.factory = factory;
    this.deadSpaceThreshold = deadSpaceThreshold;
    createRebuildThread();
    startRebuildThread();
  }
 
  public RebuildingDbService(File dataRoot, DbServiceFactory dbFactory) {
    this(dataRoot, dbFactory, 2000);
  }

  private void createRebuildThread() {
    rebuildingThread = new RebuildingThread();
    rebuildingThread.setName("RebuildingThread:"+homeDir);
    rebuildingThread.setDaemon(true);
  }

  private void readObject(ObjectInputStream in) {
    try {
      in.defaultReadObject();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    if (nextGeneration != null) { // delete partially completed regeneration (though it would be nice to continue it somehow)
      nextGeneration.drop(); // TODO test that this works
    }
    createRebuildThread();
    startRebuildThread();
  }
 
  public DbServiceFactory getFactory() {
    return factory;
  }

  public void setFactory(DbServiceFactory factory) {
    this.factory = factory;
  }

  public void rebuildPrimarySegment(int flushCount) {
    long readTxnId = subService.getHighestCompletedTxnId();
    startRebuild();
    AutocommitBuffer buffer = new AutocommitBuffer(nextGeneration, flushCount);
    for(String bag : subService.getBags(readTxnId)) {
      log(Level.FINER, "Starting rebuild for bag \""+bag+"\"");
      Iterator i = subService.query(bag, readTxnId, Util.literalMap(), Util.literalSMap());
      while(i.hasNext()) {
        DbResultMapView view = (DbResultMapView)i.next();
        Map retCopy = (Map)DbServiceUtil.deepCopyResult(view);
//        System.out.println("REBUILD :"+txnSpace+" : "+retCopy.toString());
        buffer.putEntryIntoBag(retCopy, bag);
      }
    }
    buffer.flush();
  }

  public boolean rebuildCatchupIter() {
    try {
      boolean isFinished = swapIter();
      if (isFinished) return true;
      while(processlog.readBoolean()) {
        processlog.readLong(); // txn id
        log(Level.FINE, "Rebuild: started batch object read");
        Map<String, ApiBuffer> batch = (Map<String, ApiBuffer>)processlog.readObject();
        log(Level.FINE, "Rebuild: about to commit");
        nextGeneration.commit(nextGeneration.getHighestCompletedTxnId(), batch);
        log(Level.FINE, "Rebuild: incremental commit complete");
      }
      processlog.close();
      return false;
    } catch(IOException e) {
      throw new RuntimeException(e);
    } catch(ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }
 
  public void runRebuild() {
    rebuildPrimarySegment(1000);
    // catch up loop
    while(! rebuildCatchupIter()) {}
    log(Level.FINE, "Rebuild complete");
  }
 
  public boolean isRebuilding() {
    return (nextGeneration != null);
  }
 
  public void triggerRebuild() {
    this.deadSpaceCount += this.deadSpaceThreshold;
    synchronized(rebuildingThread) {
      rebuildingThread.notifyAll();
    }
  }
 
  class RebuildingThread extends Thread {

    protected long lastBuild;
    protected long timeDelta;
    public void run() {
      while(true) {
        lastBuild = System.currentTimeMillis();
        timeDelta = 0;
        try {
          synchronized(this) {
            wait(3600);
          }
          if (rebuildingThread != this) {
            break;
          }
          timeDelta = System.currentTimeMillis() - lastBuild;
          if (isPastThreshold()) {
            deadSpaceCount = 0;
            runRebuild();
            lastBuild = System.currentTimeMillis();
          } else if (deadSpaceCount > 0) {
            deadSpaceCount++;
          }
        } catch (Exception e) {
          Util.logger.log(Level.SEVERE, "Unexpected rebuild error for "+homeDir+".  Rebuilding has been suspended for the life of this process.", e);
          throw new RuntimeException(e);
        }
      }
    }

    public boolean isPastThreshold() {
      return (deadSpaceCount + timeDelta/3600 >= deadSpaceThreshold);
    }
  }
 
  public synchronized void startRebuild() {
    String nextHome = Util.genUUID();
    nextGeneration = makeWithHome(factory, homeDir, nextHome, this);
    log(Level.FINE, "Rebuild started into this subdirectory: "+nextHome);
    nextGeneration = new TxnSpacedDbService(nextGeneration, txnSpace+txnSpaceIncr, txnSpace+2*txnSpaceIncr);
    nextGeneration.initialize();
    processlog = null;
    backlog = null;
    setDirty();
  }
  public synchronized boolean swapIter() throws IOException {
    File deleteHome = null;
    deleteProcesslog();
    if (backlog == null) {
      // we're done
      deleteHome = subService.getHomeDir();
      subService = nextGeneration;
      txnSpace += txnSpaceIncr;
      nextGeneration = null;
      notifyAll();
    } else {
      // not done yet
      flipBacklog();
    }
    setDirty();
    if (deleteHome != null) {
      deleteProcesslog();
      Util.deleteDirectory(deleteHome);
      return true;
    } else {
      return false;
    }
  }
 
  public synchronized long commit(long fromTxnId, Map<String, ApiBuffer> batch) {
    if (nextGeneration != null && backlog == null) {
      try {
        this.wait(cutover_delay_prevention_millis); // prefer to cut over to new segment
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
    int deadSpaceIncr = 0;
    for(ApiBuffer buf : batch.values()) {
      deadSpaceIncr += buf.estimatedNewDeadSpace();
    }
    long commitTxnId = subService.commit(fromTxnId, batch); // note this alters <batch>
    if (nextGeneration != null) {
      try {
        if (backlog == null) {
          makeBacklog();
        }
        backlog.writeBoolean(true);
        backlog.writeLong(commitTxnId);
        if (batch instanceof Serializable) {
          backlog.writeObject((Serializable)batch);
        } else {
          backlog.writeObject(new HashMap<String,ApiBuffer>(batch));
        }
      } catch(IOException e) {
        throw new RuntimeException(e);
      }
    }
    deadSpaceCount += deadSpaceIncr;
    synchronized(rebuildingThread) {
      if (rebuildingThread.isPastThreshold()) {
        rebuildingThread.notifyAll();
      }
    }
    return commitTxnId;
  }

  protected void openBacklog() {
    try {
      if (backlogFile != null) {
        backlog = new ObjectOutputStream(new FileOutputStream(backlogFile));
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  private void makeBacklog() {
    try {
      backlogFile = File.createTempFile("rebuild_backlog", "blg", homeDir);
      Util.logger.log(Level.FINER, "New backlog file: "+backlogFile);
      setDirty();
      openBacklog();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  private void deleteProcesslog() {
    if (processlogFile != null) {
      if (!processlogFile.delete()) {
        throw new RuntimeException("Unable to delete backlog file "+processlogFile);
      }
      processlogFile = null;
      setDirty();
    }
  }
  protected void openProcesslog() {
    try {
      if (processlogFile != null) {
        processlog = new ObjectInputStream(new FileInputStream(processlogFile));
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  private void flipBacklog() {
    try {
      backlog.writeBoolean(false);
      backlog.close();
      backlog = null;
      log(Level.FINER, "Flipped to next file: "+backlogFile);
      processlogFile = backlogFile;
      setDirty();
      openProcesslog();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  public synchronized void rollback(long commitId) throws TxnNotFoundException {
    // handle this with an event in the backlog
    throw new RuntimeException("ned to handle (how do i rollback a commit in the backlog?)");
//    subService.rollback(commitId);
//    if (nextGeneration != null) {
//      try { // this could fail
//        nextGeneration.rollback(commitId);
//      } catch(TxnNotFoundException e) {
//      }
//    }
  }

  public void dropBag(String bagName) {
    // handle this with an event in the backlog
    subService.dropBag(bagName);
    if (nextGeneration != null) {
      nextGeneration.dropBag(bagName);
    }
    throw new RuntimeException("Need to drop any entries in the backlog");
  }
 
  public void drop() {
    // handle this with an event in the backlog?
    subService.drop();
    if (nextGeneration != null) {
      nextGeneration.drop();
    }
    stopRebuildThread();
    super.drop();
  }

  public File getHomeDir() { return homeDir; }
 
  public void startRebuildThread() {
    rebuildingThread.start();
  }

  public void stopRebuildThread() {
    Thread oldThread = rebuildingThread;
    createRebuildThread();
    synchronized(oldThread) {
      oldThread.notifyAll();
    }
    try {
      oldThread.join();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }

  public RebuildingThread getRebuildThread() {
    return rebuildingThread;
  }

}
TOP

Related Classes of dovetaildb.dbservice.RebuildingDbService$RebuildingThread

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.