package com.sleepycat.je.recovery; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Level; import com.sleepycat.je.CheckpointConfig; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.DbInternal; import com.sleepycat.je.cleaner.Cleaner; import com.sleepycat.je.cleaner.TrackedFileSummary; import com.sleepycat.je.cleaner.UtilizationProfile; import com.sleepycat.je.config.EnvironmentParams; import com.sleepycat.je.dbi.DatabaseImpl; import com.sleepycat.je.dbi.DbConfigManager; import com.sleepycat.je.dbi.DbTree; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.dbi.INList; import com.sleepycat.je.dbi.MemoryBudget; import com.sleepycat.je.log.LogManager; import com.sleepycat.je.tree.BIN; import com.sleepycat.je.tree.IN; import com.sleepycat.je.tree.Node; import com.sleepycat.je.tree.SearchResult; import com.sleepycat.je.tree.Tree; import com.sleepycat.je.utilint.DaemonThread; import com.sleepycat.je.utilint.DbLsn; import com.sleepycat.je.utilint.PropUtil; import com.sleepycat.je.utilint.Tracer; import de.ovgu.cide.jakutil.*; /** * The Checkpointer looks through the tree for internal nodes that must be * flushed to the log. Checkpoint flushes must be done in ascending order from * the bottom of the tree up. */ public class Checkpointer { private EnvironmentImpl envImpl; private LogManager logManager; private long checkpointId; private long logFileMax; private long lastCheckpointMillis; private long lastFirstActiveLsn; private long lastCheckpointEnd; private volatile int highestFlushLevel; public Checkpointer( EnvironmentImpl envImpl, long waitTime, String name) throws DatabaseException { this.hook538(envImpl,waitTime,name); this.envImpl=envImpl; this.hook539(envImpl); logFileMax=envImpl.getConfigManager().getLong(EnvironmentParams.LOG_FILE_MAX); this.hook531(); this.hook545(waitTime); lastCheckpointMillis=0; highestFlushLevel=IN.MIN_LEVEL; logManager=envImpl.getLogManager(); } public int getHighestFlushLevel(){ return highestFlushLevel; } /** * Figure out the wakeup period. Supplied through this static method because * we need to pass wakeup period to the superclass and need to do the * calcuation outside this constructor. */ public static long getWakeupPeriod( DbConfigManager configManager) throws IllegalArgumentException, DatabaseException { return new Checkpointer_getWakeupPeriod(configManager).execute(); } /** * Set checkpoint id -- can only be done after recovery. */ synchronized public void setCheckpointId( long lastCheckpointId){ checkpointId=lastCheckpointId; } /** * @return the first active LSN point of the last completed checkpoint. If * no checkpoint has run, return null. */ public long getFirstActiveLsn(){ return lastFirstActiveLsn; } /** * Initialize the FirstActiveLsn during recovery. The cleaner needs this. */ public void setFirstActiveLsn( long lastFirstActiveLsn){ this.lastFirstActiveLsn=lastFirstActiveLsn; } /** * Determine whether a checkpoint should be run. * 1. If the force parameter is specified, always checkpoint. * 2. If the config object specifies time or log size, use that. * 3. If the environment is configured to use log size based checkpointing, * check the log. * 4. Lastly, use time based checking. */ private boolean isRunnable( CheckpointConfig config) throws DatabaseException { return new Checkpointer_isRunnable(this,config).execute(); } /** * The real work to do a checkpoint. This may be called by the checkpoint * thread when waking up, or it may be invoked programatically through the * api. * @param allowDeltasif true, this checkpoint may opt to log BIN deltas instead of * the full node. * @param flushAllif true, this checkpoint must flush all the way to the top of * the dbtree, instead of stopping at the highest level last * modified. * @param invokingSourcea debug aid, to indicate who invoked this checkpoint. (i.e. * recovery, the checkpointer daemon, the cleaner, * programatically) */ public synchronized void doCheckpoint( CheckpointConfig config, boolean flushAll, String invokingSource) throws DatabaseException { new Checkpointer_doCheckpoint(this,config,flushAll,invokingSource).execute(); } /** * Flush a FileSummaryLN node for each TrackedFileSummary that is currently * active. Tell the UtilizationProfile about the updated file summary. */ private void flushUtilizationInfo() throws DatabaseException { if (!DbInternal.getCheckpointUP(envImpl.getConfigManager().getEnvironmentConfig())) { return; } UtilizationProfile profile=envImpl.getUtilizationProfile(); TrackedFileSummary[] activeFiles=envImpl.getUtilizationTracker().getTrackedFiles(); for (int i=0; i < activeFiles.length; i+=1) { profile.flushFileSummary(activeFiles[i]); } } /** * Flush the nodes in order, from the lowest level to highest level. As a * flush dirties its parent, add it to the dirty map, thereby cascading the * writes up the tree. If flushAll wasn't specified, we need only cascade up * to the highest level set at the start of checkpointing. * Note that all but the top level INs and the BINDeltas are logged * provisionally. That's because we don't need to process lower INs because * the higher INs will end up pointing at them. */ private void flushDirtyNodes( SortedMap dirtyMap, boolean flushAll, boolean allowDeltas, boolean flushExtraLevel, long checkpointStart) throws DatabaseException { while (dirtyMap.size() > 0) { Integer currentLevel=(Integer)dirtyMap.firstKey(); boolean logProvisionally=(currentLevel.intValue() != highestFlushLevel); Set nodeSet=(Set)dirtyMap.get(currentLevel); Iterator iter=nodeSet.iterator(); while (iter.hasNext()) { CheckpointReference targetRef=(CheckpointReference)iter.next(); this.hook520(); this.hook546(dirtyMap,allowDeltas,checkpointStart,currentLevel,logProvisionally,targetRef); iter.remove(); } dirtyMap.remove(currentLevel); if (currentLevel.intValue() == highestFlushLevel) { break; } } } /** * Scan the INList for all dirty INs. Arrange them in level sorted map for * level ordered flushing. */ private SortedMap selectDirtyINs( boolean flushAll, boolean flushExtraLevel) throws DatabaseException { return new Checkpointer_selectDirtyINs(this,flushAll,flushExtraLevel).execute(); } /** * Flush the target IN. */ private void flushIN( CheckpointReference targetRef, Map dirtyMap, int currentLevel, boolean logProvisionally, boolean allowDeltas, long checkpointStart) throws DatabaseException { Tree tree=targetRef.db.getTree(); boolean targetWasRoot=false; if (targetRef.isDbRoot) { RootFlusher flusher=new RootFlusher(targetRef.db,logManager,targetRef.nodeId); tree.withRootLatchedExclusive(flusher); boolean flushed=flusher.getFlushed(); targetWasRoot=flusher.stillRoot(); if (flushed) { DbTree dbTree=targetRef.db.getDbEnvironment().getDbMapTree(); dbTree.modifyDbRoot(targetRef.db); this.hook532(); } } if (!targetWasRoot) { SearchResult result=tree.getParentINForChildIN(targetRef.nodeId,targetRef.containsDuplicates,false,targetRef.mainTreeKey,targetRef.dupTreeKey,false,false,-1,null,false); if (result.parent != null) { boolean mustLogParent=false; this.hook526(targetRef,dirtyMap,currentLevel,logProvisionally,allowDeltas,checkpointStart,tree,result,mustLogParent); } } } /** * @return true if this parent is appropriately 1 level above the child. */ private boolean checkParentChildRelationship( SearchResult result, int childLevel){ if (result.childNotResident && !result.exactParentFound) { return true; } int parentLevel=result.parent.getLevel(); boolean isMapTree=(childLevel & IN.DBMAP_LEVEL) != 0; boolean isMainTree=(childLevel & IN.MAIN_LEVEL) != 0; boolean checkOk=false; if (isMapTree || isMainTree) { if (parentLevel == (childLevel + 1)) { checkOk=true; } } else { if (childLevel == 1) { if (parentLevel == 2) { checkOk=true; } } else { if ((parentLevel == IN.BIN_LEVEL) || (parentLevel == childLevel + 1)) { checkOk=true; } } } return checkOk; } private String dumpParentChildInfo( SearchResult result, IN parent, long childNodeId, int currentLevel, Tree tree) throws DatabaseException { StringBuffer sb=new StringBuffer(); sb.append("ckptId=").append(checkpointId); sb.append(" result=").append(result); sb.append(" parent node=").append(parent.getNodeId()); sb.append(" level=").append(parent.getLevel()); sb.append(" child node=").append(childNodeId); sb.append(" level=").append(currentLevel); return sb.toString(); } private boolean logTargetAndUpdateParent( IN target, IN parent, int index, boolean allowDeltas, long checkpointStart, boolean logProvisionally) throws DatabaseException { target.latch(false); long newLsn=DbLsn.NULL_LSN; boolean mustLogParent=true; this.hook527(target,parent,allowDeltas,checkpointStart,logProvisionally,newLsn,mustLogParent); if (newLsn != DbLsn.NULL_LSN) { this.hook533(target); parent.updateEntry(index,newLsn); } return mustLogParent; } /** * Add a node to the dirty map. The dirty map is keyed by level (Integers) * and holds sets of IN references. */ private void addToDirtyMap( Map dirtyMap, IN in){ Integer inLevel=new Integer(in.getLevel()); Set inSet=(Set)dirtyMap.get(inLevel); if (inSet == null) { inSet=new HashSet(); dirtyMap.put(inLevel,inSet); } inSet.add(new CheckpointReference(in.getDatabase(),in.getNodeId(),in.containsDuplicates(),in.isDbRoot(),in.getMainTreeKey(),in.getDupTreeKey())); } public static class CheckpointReference { DatabaseImpl db; long nodeId; boolean containsDuplicates; boolean isDbRoot; byte[] mainTreeKey; byte[] dupTreeKey; public CheckpointReference( DatabaseImpl db, long nodeId, boolean containsDuplicates, boolean isDbRoot, byte[] mainTreeKey, byte[] dupTreeKey){ this.db=db; this.nodeId=nodeId; this.containsDuplicates=containsDuplicates; this.isDbRoot=isDbRoot; this.mainTreeKey=mainTreeKey; this.dupTreeKey=dupTreeKey; } public boolean equals( Object o){ if (!(o instanceof CheckpointReference)) { return false; } CheckpointReference other=(CheckpointReference)o; return nodeId == other.nodeId; } public int hashCode(){ return (int)nodeId; } } @MethodObject static class Checkpointer_getWakeupPeriod { Checkpointer_getWakeupPeriod( DbConfigManager configManager){ this.configManager=configManager; } long execute() throws IllegalArgumentException, DatabaseException { this.hook541(); this.hook519(); result=0; this.hook540(); return result; } protected DbConfigManager configManager; protected long wakeupPeriod; protected long bytePeriod; protected int result; protected void hook519() throws IllegalArgumentException, DatabaseException { } protected void hook540() throws IllegalArgumentException, DatabaseException { } protected void hook541() throws IllegalArgumentException, DatabaseException { } } @MethodObject static class Checkpointer_isRunnable { Checkpointer_isRunnable( Checkpointer _this, CheckpointConfig config){ this._this=_this; this.config=config; } boolean execute() throws DatabaseException { try { useBytesInterval=0; useTimeInterval=0; nextLsn=DbLsn.NULL_LSN; try { if (config.getForce()) { return true; } else { this.hook543(); } this.hook542(); } finally { this.hook521(); } throw ReturnHack.returnBoolean; } catch ( ReturnBoolean r) { return r.value; } } protected Checkpointer _this; protected CheckpointConfig config; protected long useBytesInterval; protected long useTimeInterval; protected long nextLsn; protected long lastUsedLsn; protected StringBuffer sb; protected void hook521() throws DatabaseException { } protected void hook542() throws DatabaseException { throw new ReturnBoolean(false); } protected void hook543() throws DatabaseException { this.hook544(); } protected void hook544() throws DatabaseException { } } @MethodObject static class Checkpointer_doCheckpoint { Checkpointer_doCheckpoint( Checkpointer _this, CheckpointConfig config, boolean flushAll, String invokingSource){ this._this=_this; this.config=config; this.flushAll=flushAll; this.invokingSource=invokingSource; } void execute() throws DatabaseException { if (_this.envImpl.isReadOnly()) { return; } if (!_this.isRunnable(config)) { return; } flushExtraLevel=false; cleaner=_this.envImpl.getCleaner(); cleanerFiles=cleaner.getFilesAtCheckpointStart(); if (cleanerFiles != null) { flushExtraLevel=true; } _this.lastCheckpointMillis=System.currentTimeMillis(); this.hook535(); _this.checkpointId++; this.hook534(); success=false; this.hook522(); this.hook548(); mb=_this.envImpl.getMemoryBudget(); try { this.hook525(); } finally { this.hook549(); this.hook524(); } } protected Checkpointer _this; protected CheckpointConfig config; protected boolean flushAll; protected String invokingSource; protected boolean flushExtraLevel; protected Cleaner cleaner; protected Set[] cleanerFiles; protected boolean success; protected boolean traced; protected int dirtyMapMemSize; protected MemoryBudget mb; protected long checkpointStart; protected long firstActiveLsn; protected SortedMap dirtyMap; protected CheckpointStart startEntry; protected int totalSize; protected Set nodeSet; protected int size; protected boolean allowDeltas; protected CheckpointEnd endEntry; protected void hook522() throws DatabaseException { } protected void hook523() throws DatabaseException { } protected void hook524() throws DatabaseException { } protected void hook525() throws DatabaseException { checkpointStart=DbLsn.NULL_LSN; firstActiveLsn=DbLsn.NULL_LSN; dirtyMap=null; this.hook547(); this.hook551(); for (Iterator i=dirtyMap.values().iterator(); i.hasNext(); ) { nodeSet=(Set)i.next(); this.hook552(); } this.hook550(); allowDeltas=!config.getMinimizeRecoveryTime(); _this.flushDirtyNodes(dirtyMap,flushAll,allowDeltas,flushExtraLevel,checkpointStart); _this.flushUtilizationInfo(); endEntry=new CheckpointEnd(invokingSource,checkpointStart,_this.envImpl.getRootLsn(),firstActiveLsn,Node.getLastId(),_this.envImpl.getDbMapTree().getLastDbId(),_this.envImpl.getTxnManager().getLastTxnId(),_this.checkpointId); this.hook523(); _this.lastCheckpointEnd=_this.logManager.logForceFlush(endEntry,true); _this.lastFirstActiveLsn=firstActiveLsn; this.hook536(); _this.highestFlushLevel=IN.MIN_LEVEL; success=true; if (cleanerFiles != null) { cleaner.updateFilesAtCheckpointEnd(cleanerFiles); } } protected void hook534() throws DatabaseException { } protected void hook535() throws DatabaseException { } protected void hook536() throws DatabaseException { } protected void hook547() throws DatabaseException { startEntry=new CheckpointStart(_this.checkpointId,invokingSource); checkpointStart=_this.logManager.log(startEntry); firstActiveLsn=_this.envImpl.getTxnManager().getFirstActiveLsn(); if (firstActiveLsn == DbLsn.NULL_LSN) { firstActiveLsn=checkpointStart; } else { if (DbLsn.compareTo(checkpointStart,firstActiveLsn) < 0) { firstActiveLsn=checkpointStart; } } dirtyMap=_this.selectDirtyINs(flushAll,flushExtraLevel); } protected void hook548() throws DatabaseException { } protected void hook549() throws DatabaseException { } protected void hook550() throws DatabaseException { } protected void hook551() throws DatabaseException { } protected void hook552() throws DatabaseException { } } @MethodObject static class Checkpointer_selectDirtyINs { Checkpointer_selectDirtyINs( Checkpointer _this, boolean flushAll, boolean flushExtraLevel){ this._this=_this; this.flushAll=flushAll; this.flushExtraLevel=flushExtraLevel; } SortedMap execute() throws DatabaseException { newDirtyMap=new TreeMap(); inMemINs=_this.envImpl.getInMemoryINs(); this.hook529(); this.hook553(); this.hook528(); return newDirtyMap; } protected Checkpointer _this; protected boolean flushAll; protected boolean flushExtraLevel; protected SortedMap newDirtyMap; protected INList inMemINs; protected long totalSize; protected MemoryBudget mb; protected Iterator iter; protected IN in; protected Integer level; protected Set dirtySet; protected void hook528() throws DatabaseException { iter=inMemINs.iterator(); while (iter.hasNext()) { in=(IN)iter.next(); in.latch(false); this.hook530(); } this.hook554(); if (newDirtyMap.size() > 0) { if (flushAll) { _this.highestFlushLevel=_this.envImpl.getDbMapTree().getHighestLevel(); } else { _this.highestFlushLevel=((Integer)newDirtyMap.lastKey()).intValue(); if (flushExtraLevel) { _this.highestFlushLevel+=1; } } } else { _this.highestFlushLevel=IN.MAX_LEVEL; } } protected void hook529() throws DatabaseException { } protected void hook530() throws DatabaseException { if (in.getDirty()) { level=new Integer(in.getLevel()); { } if (newDirtyMap.containsKey(level)) { dirtySet=(Set)newDirtyMap.get(level); } else { dirtySet=new HashSet(); newDirtyMap.put(level,dirtySet); } dirtySet.add(new CheckpointReference(in.getDatabase(),in.getNodeId(),in.containsDuplicates(),in.isDbRoot(),in.getMainTreeKey(),in.getDupTreeKey())); } } protected void hook553() throws DatabaseException { } protected void hook554() throws DatabaseException { } } protected void hook520() throws DatabaseException { } protected void hook526( CheckpointReference targetRef, Map dirtyMap, int currentLevel, boolean logProvisionally, boolean allowDeltas, long checkpointStart, Tree tree, SearchResult result, boolean mustLogParent) throws DatabaseException { if (result.exactParentFound) { IN renewedTarget=(IN)result.parent.getTarget(result.index); if (renewedTarget == null) { mustLogParent=true; } else { mustLogParent=logTargetAndUpdateParent(renewedTarget,result.parent,result.index,allowDeltas,checkpointStart,logProvisionally); } } else { if (result.childNotResident) { if (result.parent.getLevel() > currentLevel) { mustLogParent=true; } } } if (mustLogParent) { assert checkParentChildRelationship(result,currentLevel) : dumpParentChildInfo(result,result.parent,targetRef.nodeId,currentLevel,tree); addToDirtyMap(dirtyMap,result.parent); } } protected void hook527( IN target, IN parent, boolean allowDeltas, long checkpointStart, boolean logProvisionally, long newLsn, boolean mustLogParent) throws DatabaseException { if (target.getDirty()) { newLsn=target.log(logManager,allowDeltas,logProvisionally,true,parent); if (allowDeltas && newLsn == DbLsn.NULL_LSN) { this.hook537(); long lastFullLsn=target.getLastFullVersion(); if (DbLsn.compareTo(lastFullLsn,checkpointStart) < 0) { mustLogParent=false; } } } } protected void hook531() throws DatabaseException { } protected void hook532() throws DatabaseException { } protected void hook533( IN target) throws DatabaseException { } protected void hook537() throws DatabaseException { } protected void hook538( EnvironmentImpl envImpl, long waitTime, String name) throws DatabaseException { } protected void hook539( EnvironmentImpl envImpl) throws DatabaseException { } protected void hook545( long waitTime) throws DatabaseException { } protected void hook546( SortedMap dirtyMap, boolean allowDeltas, long checkpointStart, Integer currentLevel, boolean logProvisionally, CheckpointReference targetRef) throws DatabaseException { } }