package com.sleepycat.je.log; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.zip.Checksum; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.RunRecoveryException; import com.sleepycat.je.config.EnvironmentParams; import com.sleepycat.je.dbi.DbConfigManager; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.log.entry.LogEntry; import com.sleepycat.je.utilint.Adler32; import com.sleepycat.je.utilint.DbLsn; import com.sleepycat.je.utilint.HexFormatter; import de.ovgu.cide.jakutil.*; /** * The FileManager presents the abstraction of one contiguous file. It doles out * LSNs. */ public class FileManager { public static class FileMode { public static final FileMode READ_MODE=new FileMode("r"); public static final FileMode READWRITE_MODE=new FileMode("rw"); private String fileModeValue; private FileMode( String fileModeValue){ this.fileModeValue=fileModeValue; } public String getModeValue(){ return fileModeValue; } } static boolean IO_EXCEPTION_TESTING=false; private static final String DEBUG_NAME=FileManager.class.getName(); private static long writeCount=0; private static long stopOnWriteCount=Long.MAX_VALUE; public static final String JE_SUFFIX=".jdb"; public static final String CIF_SUFFIX=".cif"; public static final String DEL_SUFFIX=".del"; public static final String BAD_SUFFIX=".bad"; public static final String LOCK_SUFFIX=".lck"; static final String[] DEL_SUFFIXES={DEL_SUFFIX}; static final String[] JE_SUFFIXES={JE_SUFFIX}; private static final String[] JE_AND_DEL_SUFFIXES={JE_SUFFIX,DEL_SUFFIX}; private boolean syncAtFileEnd=true; private EnvironmentImpl envImpl; private long maxFileSize; private File dbEnvHome; private boolean includeDeletedFiles=false; private boolean readOnly; private long currentFileNum; private long nextAvailableLsn; private long lastUsedLsn; private long prevOffset; private boolean forceNewFile; private long savedCurrentFileNum; private long savedNextAvailableLsn; private long savedLastUsedLsn; private long savedPrevOffset; private boolean savedForceNewFile; private LogEndFileDescriptor endOfLog; private Map perFileLastUsedLsn; /** * Set up the file cache and initialize the file manager to point to the * beginning of the log. * @param configManager * @param dbEnvHomeenvironment home directory */ public FileManager( EnvironmentImpl envImpl, File dbEnvHome, boolean readOnly) throws DatabaseException { this.envImpl=envImpl; this.dbEnvHome=dbEnvHome; this.readOnly=readOnly; DbConfigManager configManager=envImpl.getConfigManager(); maxFileSize=configManager.getLong(EnvironmentParams.LOG_FILE_MAX); this.hook456(configManager); this.hook467(readOnly); this.hook457(configManager); this.hook449(envImpl); if (!dbEnvHome.exists()) { throw new LogException("Environment home " + dbEnvHome + " doesn't exist"); } currentFileNum=0L; nextAvailableLsn=DbLsn.makeLsn(currentFileNum,firstLogEntryOffset()); lastUsedLsn=DbLsn.NULL_LSN; perFileLastUsedLsn=new HashMap(); prevOffset=0L; endOfLog=new LogEndFileDescriptor(); forceNewFile=false; saveLastPosition(); String stopOnWriteProp=System.getProperty("je.debug.stopOnWrite"); if (stopOnWriteProp != null) { stopOnWriteCount=Long.parseLong(stopOnWriteProp); } this.hook452(envImpl); } /** * Set the file manager's "end of log". * @param nextAvailableLsnLSN to be used for the next log entry * @param lastUsedLsnlast LSN to have a valid entry, may be null * @param prevOffsetvalue to use for the prevOffset of the next entry. If the * beginning of the file, this is 0. */ public void setLastPosition( long nextAvailableLsn, long lastUsedLsn, long prevOffset){ this.lastUsedLsn=lastUsedLsn; perFileLastUsedLsn.put(new Long(DbLsn.getFileNumber(lastUsedLsn)),new Long(lastUsedLsn)); this.nextAvailableLsn=nextAvailableLsn; currentFileNum=DbLsn.getFileNumber(this.nextAvailableLsn); this.prevOffset=prevOffset; saveLastPosition(); } void saveLastPosition(){ savedNextAvailableLsn=nextAvailableLsn; savedLastUsedLsn=lastUsedLsn; savedPrevOffset=prevOffset; savedForceNewFile=forceNewFile; savedCurrentFileNum=currentFileNum; } void restoreLastPosition(){ nextAvailableLsn=savedNextAvailableLsn; lastUsedLsn=savedLastUsedLsn; prevOffset=savedPrevOffset; forceNewFile=savedForceNewFile; currentFileNum=savedCurrentFileNum; } /** * May be used to disable sync at file end to speed unit tests. Must only be * used for unit testing, since log corruption may result. */ public void setSyncAtFileEnd( boolean sync){ syncAtFileEnd=sync; } /** * public for cleaner. * @return the number of the first file in this environment. */ public Long getFirstFileNum(){ return getFileNum(true); } public boolean getReadOnly(){ return readOnly; } /** * @return the number of the last file in this environment. */ public Long getLastFileNum(){ return getFileNum(false); } public long getCurrentFileNum(){ return currentFileNum; } public void setIncludeDeletedFiles( boolean includeDeletedFiles){ this.includeDeletedFiles=includeDeletedFiles; } /** * Get all JE file numbers. * @return an array of all JE file numbers. */ public Long[] getAllFileNumbers(){ String[] names=listFiles(JE_SUFFIXES); Long[] nums=new Long[names.length]; for (int i=0; i < nums.length; i+=1) { nums[i]=getNumFromName(names[i]); } return nums; } /** * Get the next file number before/after currentFileNum. * @param currentFileNumthe file we're at right now. Note that it may not exist, if * it's been cleaned and renamed. * @param forwardif true, we want the next larger file, if false we want the * previous file * @return null if there is no following file, or if filenum doesn't exist */ public Long getFollowingFileNum( long currentFileNum, boolean forward){ String[] names=listFiles(JE_SUFFIXES); String searchName=getFileName(currentFileNum,JE_SUFFIX); int foundIdx=Arrays.binarySearch(names,searchName); boolean foundTarget=false; if (foundIdx >= 0) { if (forward) { foundIdx++; } else { foundIdx--; } } else { foundIdx=Math.abs(foundIdx + 1); if (!forward) { foundIdx--; } } if (forward && (foundIdx < names.length)) { foundTarget=true; } else if (!forward && (foundIdx > -1)) { foundTarget=true; } if (foundTarget) { return getNumFromName(names[foundIdx]); } else { return null; } } /** * @return true if there are any files at all. */ public boolean filesExist(){ String[] names=listFiles(JE_SUFFIXES); return (names.length != 0); } /** * Get the first or last file number in the set of je files. * @param firstif true, get the first file, else get the last file * @return the file number or null if no files exist */ private Long getFileNum( boolean first){ String[] names=listFiles(JE_SUFFIXES); if (names.length == 0) { return null; } else { int index=0; if (!first) { index=names.length - 1; } return getNumFromName(names[index]); } } /** * Get the file number from a file name. * @param thefile name * @return the file number */ private Long getNumFromName( String fileName){ String fileNumber=fileName.substring(0,fileName.indexOf(".")); return new Long(Long.parseLong(fileNumber,16)); } /** * Find je files. Return names sorted in ascending fashion. * @param suffixwhich type of file we're looking for * @return array of file names */ String[] listFiles( String[] suffixes){ String[] fileNames=dbEnvHome.list(new JEFileFilter(suffixes)); Arrays.sort(fileNames); return fileNames; } /** * Find je files, flavor for unit test support. * @param suffixwhich type of file we're looking for * @return array of file names */ public static String[] listFiles( File envDirFile, String[] suffixes){ String[] fileNames=envDirFile.list(new JEFileFilter(suffixes)); Arrays.sort(fileNames); return fileNames; } /** * @return the full file name and path for the nth je file. */ String[] getFullFileNames( long fileNum){ if (includeDeletedFiles) { int nSuffixes=JE_AND_DEL_SUFFIXES.length; String[] ret=new String[nSuffixes]; for (int i=0; i < nSuffixes; i++) { ret[i]=getFullFileName(getFileName(fileNum,JE_AND_DEL_SUFFIXES[i])); } return ret; } else { return new String[]{getFullFileName(getFileName(fileNum,JE_SUFFIX))}; } } /** * @return the full file name and path for the given file number and suffix. */ public String getFullFileName( long fileNum, String suffix){ return getFullFileName(getFileName(fileNum,suffix)); } /** * @return the full file name and path for this file name. */ private String getFullFileName( String fileName){ return dbEnvHome + File.separator + fileName; } /** * @return the file name for the nth file. */ public static String getFileName( long fileNum, String suffix){ return (HexFormatter.formatLong(fileNum).substring(10) + suffix); } /** * Rename this file to NNNNNNNN.suffix. If that file already exists, try * NNNNNNNN.suffix.1, etc. Used for deleting files or moving corrupt files * aside. * @param fileNumthe file we want to move * @param newSuffixthe new file suffix */ public void renameFile( long fileNum, String newSuffix) throws DatabaseException, IOException { int repeatNum=0; boolean renamed=false; while (!renamed) { String generation=""; if (repeatNum > 0) { generation="." + repeatNum; } String newName=getFullFileName(getFileName(fileNum,newSuffix) + generation); File targetFile=new File(newName); if (targetFile.exists()) { repeatNum++; } else { String oldFileName=getFullFileNames(fileNum)[0]; this.hook458(fileNum); File oldFile=new File(oldFileName); if (oldFile.renameTo(targetFile)) { renamed=true; } else { throw new LogException("Couldn't rename " + oldFileName + " to "+ newName); } } } } /** * Delete log file NNNNNNNN. * @param fileNumthe file we want to move */ public void deleteFile( long fileNum) throws DatabaseException, IOException { String fileName=getFullFileNames(fileNum)[0]; this.hook459(fileNum); File file=new File(fileName); boolean done=file.delete(); if (!done) { throw new LogException("Couldn't delete " + file); } } /** * Return a read only file handle that corresponds the this file number. * Retrieve it from the cache or open it anew and validate the file header. * This method takes a latch on this file, so that the file descriptor will * be held in the cache as long as it's in use. When the user is done with * the file, the latch must be released. * @param fileNumwhich file * @return the file handle for the existing or newly created file */ FileHandle getFileHandle( long fileNum) throws LogException, DatabaseException { try { Long fileId=new Long(fileNum); FileHandle fileHandle=null; this.hook460(fileNum,fileId,fileHandle); throw ReturnHack.returnObject; } catch ( ReturnObject r) { return (FileHandle)r.value; } } private FileHandle makeFileHandle( long fileNum, FileMode mode) throws DatabaseException { String[] fileNames=getFullFileNames(fileNum); RandomAccessFile newFile=null; String fileName=null; try { FileNotFoundException FNFE=null; for (int i=0; i < fileNames.length; i++) { fileName=fileNames[i]; try { newFile=new RandomAccessFile(fileName,mode.getModeValue()); break; } catch ( FileNotFoundException e) { if (FNFE == null) { FNFE=e; } } } if (newFile == null) { throw FNFE; } boolean oldHeaderVersion=false; if (newFile.length() == 0) { if (mode == FileMode.READWRITE_MODE) { long lastLsn=DbLsn.longToLsn((Long)perFileLastUsedLsn.remove(new Long(fileNum - 1))); long headerPrevOffset=0; if (lastLsn != DbLsn.NULL_LSN) { headerPrevOffset=DbLsn.getFileOffset(lastLsn); } FileHeader fileHeader=new FileHeader(fileNum,headerPrevOffset); writeFileHeader(newFile,fileName,fileHeader); } } else { oldHeaderVersion=readAndValidateFileHeader(newFile,fileName,fileNum); } return new FileHandle(newFile,fileName,envImpl,oldHeaderVersion); } catch ( FileNotFoundException e) { throw new LogFileNotFoundException("Couldn't open file " + fileName + ": "+ e.getMessage()); } catch ( DbChecksumException e) { closeFileInErrorCase(newFile); throw new DbChecksumException(envImpl,"Couldn't open file " + fileName,e); } catch ( Throwable t) { closeFileInErrorCase(newFile); throw new DatabaseException("Couldn't open file " + fileName + ": "+ t,t); } } /** * Close this file and eat any exceptions. Used in catch clauses. */ private void closeFileInErrorCase( RandomAccessFile file){ try { if (file != null) { file.close(); } } catch ( IOException e) { } } /** * Read the given je log file and validate the header. * @throws DatabaseExceptionif the file header isn't valid * @return whether the file header has an old version number. */ private boolean readAndValidateFileHeader( RandomAccessFile file, String fileName, long fileNum) throws DatabaseException, IOException { LogManager logManager=envImpl.getLogManager(); LogEntry headerEntry=logManager.getLogEntry(DbLsn.makeLsn(fileNum,0),file); FileHeader header=(FileHeader)headerEntry.getMainItem(); return header.validate(fileName,fileNum); } /** * Write a proper file header to the given file. */ private void writeFileHeader( RandomAccessFile file, String fileName, FileHeader header) throws DatabaseException, IOException { envImpl.checkIfInvalid(); if (envImpl.mayNotWrite()) { return; } int headerSize=header.getLogSize(); int entrySize=headerSize + LogManager.HEADER_BYTES; ByteBuffer headerBuf=envImpl.getLogManager().putIntoBuffer(header,headerSize,0,false,entrySize); if (++writeCount >= stopOnWriteCount) { Runtime.getRuntime().halt(0xff); } int bytesWritten; try { if (RUNRECOVERY_EXCEPTION_TESTING) { generateRunRecoveryException(file,headerBuf,0); } bytesWritten=writeToFile(file,headerBuf,0); } catch ( ClosedChannelException e) { throw new RunRecoveryException(envImpl,"Channel closed, may be due to thread interrupt",e); } catch ( IOException e) { throw new RunRecoveryException(envImpl,"IOException caught: " + e); } if (bytesWritten != entrySize) { throw new LogException("File " + fileName + " was created with an incomplete header. Only "+ bytesWritten+ " bytes were written."); } } /** * @return the prevOffset field stored in the file header. */ long getFileHeaderPrevOffset( long fileNum) throws IOException, DatabaseException { LogEntry headerEntry=envImpl.getLogManager().getLogEntry(DbLsn.makeLsn(fileNum,0)); FileHeader header=(FileHeader)headerEntry.getMainItem(); return header.getLastEntryInPrevFileOffset(); } /** * @return the file offset of the last LSN that was used. For constructing * the headers of log entries. If the last LSN that was used was in * a previous file, or this is the very first LSN of the whole * system, return 0. */ long getPrevEntryOffset(){ return prevOffset; } /** * Increase the current log position by "size" bytes. Move the prevOffset * pointer along. * @param sizeis an unsigned int * @return true if we flipped to the next log file. */ boolean bumpLsn( long size){ saveLastPosition(); boolean flippedFiles=false; if (forceNewFile || (DbLsn.getFileOffset(nextAvailableLsn) + size) > maxFileSize) { forceNewFile=false; currentFileNum++; if (lastUsedLsn != DbLsn.NULL_LSN) { perFileLastUsedLsn.put(new Long(DbLsn.getFileNumber(lastUsedLsn)),new Long(lastUsedLsn)); } prevOffset=0; lastUsedLsn=DbLsn.makeLsn(currentFileNum,firstLogEntryOffset()); flippedFiles=true; } else { if (lastUsedLsn == DbLsn.NULL_LSN) { prevOffset=0; } else { prevOffset=DbLsn.getFileOffset(lastUsedLsn); } lastUsedLsn=nextAvailableLsn; } nextAvailableLsn=DbLsn.makeLsn(DbLsn.getFileNumber(lastUsedLsn),(DbLsn.getFileOffset(lastUsedLsn) + size)); return flippedFiles; } /** * Write out a log buffer to the file. * @param fullBufferbuffer to write */ void writeLogBuffer( LogBuffer fullBuffer) throws DatabaseException { envImpl.checkIfInvalid(); if (envImpl.mayNotWrite()) { return; } long firstLsn=fullBuffer.getFirstLsn(); if (firstLsn != DbLsn.NULL_LSN) { RandomAccessFile file=endOfLog.getWritableFile(DbLsn.getFileNumber(firstLsn)); ByteBuffer data=fullBuffer.getDataBuffer(); if (++writeCount >= stopOnWriteCount) { Runtime.getRuntime().halt(0xff); } try { this.hook465(fullBuffer,firstLsn,file); if (IO_EXCEPTION_TESTING) { throw new IOException("generated for testing"); } if (RUNRECOVERY_EXCEPTION_TESTING) { generateRunRecoveryException(file,data,DbLsn.getFileOffset(firstLsn)); } writeToFile(file,data,DbLsn.getFileOffset(firstLsn)); } catch ( ClosedChannelException e) { throw new RunRecoveryException(envImpl,"File closed, may be due to thread interrupt",e); } catch ( IOException IOE) { abortCommittedTxns(data); this.hook466(fullBuffer,firstLsn,file,data,IOE); } assert EnvironmentImpl.maybeForceYield(); } } /** * Write a buffer to a file at a given offset, using NIO if so configured. */ private int writeToFile( RandomAccessFile file, ByteBuffer data, long destOffset) throws IOException, DatabaseException { return new FileManager_writeToFile(this,file,data,destOffset).execute(); } /** * Read a buffer from a file at a given offset, using NIO if so configured. */ void readFromFile( RandomAccessFile file, ByteBuffer readBuffer, long offset) throws IOException { new FileManager_readFromFile(this,file,readBuffer,offset).execute(); } private void abortCommittedTxns( ByteBuffer data){ final byte commitType=LogEntryType.LOG_TXN_COMMIT.getTypeNum(); final byte abortType=LogEntryType.LOG_TXN_ABORT.getTypeNum(); this.hook461(data); while (data.remaining() > 0) { int recStartPos=data.position(); data.position(recStartPos + LogManager.HEADER_ENTRY_TYPE_OFFSET); int typePos=data.position(); byte entryType=data.get(); boolean recomputeChecksum=false; if (entryType == commitType) { data.position(typePos); data.put(abortType); recomputeChecksum=true; } byte version=data.get(); data.position(data.position() + LogManager.PREV_BYTES); int itemSize=LogUtils.readInt(data); int itemDataStartPos=data.position(); if (recomputeChecksum) { Checksum checksum=Adler32.makeChecksum(); data.position(recStartPos); int nChecksumBytes=itemSize + (LogManager.HEADER_BYTES - LogManager.CHECKSUM_BYTES); byte[] checksumBytes=new byte[nChecksumBytes]; System.arraycopy(data.array(),recStartPos + LogManager.CHECKSUM_BYTES,checksumBytes,0,nChecksumBytes); checksum.update(checksumBytes,0,nChecksumBytes); LogUtils.writeUnsignedInt(data,checksum.getValue()); } data.position(itemDataStartPos + itemSize); } data.position(0); } /** * FSync the end of the log. */ void syncLogEnd() throws DatabaseException { try { endOfLog.force(); } catch ( IOException e) { throw new DatabaseException(e); } } /** * Sync the end of the log, close off this log file. Should only be called * under the log write latch. */ void syncLogEndAndFinishFile() throws DatabaseException, IOException { if (syncAtFileEnd) { syncLogEnd(); } endOfLog.close(); } /** * Close all file handles and empty the cache. */ public void clear() throws IOException, DatabaseException { endOfLog.close(); } /** * Clear the file lock. */ public void close() throws IOException, DatabaseException { } /** * Ensure that if the environment home dir is on readonly media or in a * readonly directory that the environment has been opened for readonly * access. * @return true if the environment home dir is readonly. */ private boolean checkEnvHomePermissions( boolean readOnly) throws DatabaseException { boolean envDirIsReadOnly=!dbEnvHome.canWrite(); if (envDirIsReadOnly && !readOnly) { throw new DatabaseException("The Environment directory " + dbEnvHome + " is not writable, but the "+ "Environment was opened for read-write access."); } return envDirIsReadOnly; } /** * Truncate a log at this position. Used by recovery to a timestamp * utilities and by recovery to set the end-of-log position. * <p> * This method forces a new log file to be written next, if the last file * (the file truncated to) has an old version in its header. This ensures * that when the log is opened by an old version of JE, a version * incompatibility will be detected. [#11243] * </p> */ public void truncateLog( long fileNum, long offset) throws IOException, DatabaseException { FileHandle handle=makeFileHandle(fileNum,FileMode.READWRITE_MODE); RandomAccessFile file=handle.getFile(); try { file.getChannel().truncate(offset); } finally { file.close(); } if (handle.isOldHeaderVersion()) { forceNewFile=true; } } /** * Set the flag that causes a new file to be written before the next write. */ void forceNewLogFile(){ forceNewFile=true; } /** * @return the size in bytes of the file header log entry. */ public static int firstLogEntryOffset(){ return FileHeader.entrySize() + LogManager.HEADER_BYTES; } /** * Return the next available LSN in the log. Note that this is * unsynchronized, so is only valid as an approximation of log size. */ public long getNextLsn(){ return nextAvailableLsn; } /** * Return the last allocated LSN in the log. Note that this is * unsynchronized, so if it is called outside the log write latch it is only * valid as an approximation of log size. */ public long getLastUsedLsn(){ return lastUsedLsn; } /** * The LogEndFileDescriptor is used to write and fsync the end of the log. * Because the JE log is append only, there is only one logical R/W file * descriptor for the whole environment. This class actually implements two * RandomAccessFile instances, one for writing and one for fsyncing, so the * two types of operations don't block each other. * The write file descriptor is considered the master. Manipulation of this * class is done under the log write latch. Here's an explanation of why the * log write latch is sufficient to safeguard all operations. * There are two types of callers who may use this file descriptor: the * thread that is currently writing to the end of the log and any threads * that are fsyncing on behalf of the FSyncManager. * The writing thread appends data to the file and fsyncs the file when we * flip over to a new log file. The file is only instantiated at the point * that it must do so -- which is either when the first fsync is required by * JE or when the log file is full and we flip files. Therefore, the writing * thread has two actions that change this descriptor -- we initialize the * file descriptor for the given log file at the first write to the file, * and we close the file descriptor when the log file is full. Therefore is * a period when there is no log descriptor -- when we have not yet written * a log buffer into a given log file. * The fsyncing threads ask for the log end file descriptor asynchronously, * but will never modify it. These threads may arrive at the point when the * file descriptor is null, and therefore skip their fysnc, but that is fine * because it means a writing thread already flipped that target file and * has moved on to the next file. * Time Activity 10 thread 1 writes log entry A into file 0x0, issues fsync * outside of log write latch, yields the processor 20 thread 2 writes log * entry B, piggybacks off thread 1 30 thread 3 writes log entry C, but no * room left in that file, so it flips the log, and fsyncs file 0x0, all * under the log write latch. It nulls out endOfLogRWFile, moves onto file * 0x1, but doesn't create the file yet. 40 thread 1 finally comes along, * but endOfLogRWFile is null-- no need to fsync in that case, 0x0 got * fsynced. */ class LogEndFileDescriptor { private RandomAccessFile endOfLogRWFile=null; private RandomAccessFile endOfLogSyncFile=null; /** * getWritableFile must be called under the log write latch. */ RandomAccessFile getWritableFile( long fileNumber) throws RunRecoveryException { try { if (endOfLogRWFile == null) { endOfLogRWFile=makeFileHandle(fileNumber,FileMode.READWRITE_MODE).getFile(); endOfLogSyncFile=makeFileHandle(fileNumber,FileMode.READWRITE_MODE).getFile(); } return endOfLogRWFile; } catch ( Exception e) { throw new RunRecoveryException(envImpl,e); } } /** * FSync the log file that makes up the end of the log. */ void force() throws DatabaseException, IOException { RandomAccessFile file=endOfLogSyncFile; if (file != null) { FileChannel channel=file.getChannel(); try { channel.force(false); } catch ( ClosedChannelException e) { throw new RunRecoveryException(envImpl,"Channel closed, may be due to thread interrupt",e); } assert EnvironmentImpl.maybeForceYield(); } } /** * Close the end of the log file descriptor. Use atomic assignment to * ensure that we won't force and close on the same descriptor. */ void close() throws IOException { IOException firstException=null; if (endOfLogRWFile != null) { RandomAccessFile file=endOfLogRWFile; endOfLogRWFile=null; try { file.close(); } catch ( IOException e) { firstException=e; } } if (endOfLogSyncFile != null) { RandomAccessFile file=endOfLogSyncFile; endOfLogSyncFile=null; file.close(); } if (firstException != null) { throw firstException; } } } static boolean RUNRECOVERY_EXCEPTION_TESTING=false; private static final int RUNRECOVERY_EXCEPTION_MAX=100; private int runRecoveryExceptionCounter=0; private boolean runRecoveryExceptionThrown=false; private Random runRecoveryExceptionRandom=null; private void generateRunRecoveryException( RandomAccessFile file, ByteBuffer data, long destOffset) throws DatabaseException, IOException { if (runRecoveryExceptionThrown) { try { throw new Exception("Write after RunRecoveryException"); } catch ( Exception e) { e.printStackTrace(); } } runRecoveryExceptionCounter+=1; if (runRecoveryExceptionCounter >= RUNRECOVERY_EXCEPTION_MAX) { runRecoveryExceptionCounter=0; } if (runRecoveryExceptionRandom == null) { runRecoveryExceptionRandom=new Random(System.currentTimeMillis()); } if (runRecoveryExceptionCounter == runRecoveryExceptionRandom.nextInt(RUNRECOVERY_EXCEPTION_MAX)) { int len=runRecoveryExceptionRandom.nextInt(data.remaining()); if (len > 0) { byte[] a=new byte[len]; data.get(a,0,len); ByteBuffer buf=ByteBuffer.wrap(a); writeToFile(file,buf,destOffset); } runRecoveryExceptionThrown=true; throw new RunRecoveryException(envImpl,"Randomly generated for testing"); } } @MethodObject static class FileManager_writeToFile { FileManager_writeToFile( FileManager _this, RandomAccessFile file, ByteBuffer data, long destOffset){ this._this=_this; this.file=file; this.data=data; this.destOffset=destOffset; } int execute() throws IOException, DatabaseException { totalBytesWritten=0; this.hook455(); this.hook445(); return totalBytesWritten; } protected FileManager _this; protected RandomAccessFile file; protected ByteBuffer data; protected long destOffset; protected int totalBytesWritten; protected FileChannel channel; protected ByteBuffer useData; protected int origLimit; protected int bytesWritten; protected int pos; protected int size; protected void hook445() throws IOException, DatabaseException { } protected void hook455() throws IOException, DatabaseException { } } @MethodObject static class FileManager_readFromFile { FileManager_readFromFile( FileManager _this, RandomAccessFile file, ByteBuffer readBuffer, long offset){ this._this=_this; this.file=file; this.readBuffer=readBuffer; this.offset=offset; } void execute() throws IOException { this.hook446(); } protected FileManager _this; protected RandomAccessFile file; protected ByteBuffer readBuffer; protected long offset; protected FileChannel channel; protected int readLength; protected long currentPosition; protected int bytesRead1; protected int pos; protected int size; protected int bytesRead2; protected void hook446() throws IOException { } } protected void hook449( EnvironmentImpl envImpl) throws DatabaseException { } protected FileHandle hook450( long fileNum, Long fileId, FileHandle fileHandle) throws LogException, DatabaseException { fileHandle=this.hook462(fileNum,fileId,fileHandle); return fileHandle; } protected void hook452( EnvironmentImpl envImpl) throws DatabaseException { } protected void hook453( FileHandle fileHandle) throws LogException, DatabaseException { } protected void hook454( FileHandle fileHandle) throws LogException, DatabaseException { } protected void hook456( DbConfigManager configManager) throws DatabaseException { } protected void hook457( DbConfigManager configManager) throws DatabaseException { } protected void hook458( long fileNum) throws DatabaseException, IOException { } protected void hook459( long fileNum) throws DatabaseException, IOException { } protected void hook460( long fileNum, Long fileId, FileHandle fileHandle) throws LogException, DatabaseException { fileHandle=this.hook463(fileNum,fileId,fileHandle); this.hook453(fileHandle); if (fileHandle.getFile() == null) { this.hook454(fileHandle); } else { throw new ReturnObject(fileHandle); } } protected void hook461( ByteBuffer data){ } protected FileHandle hook462( long fileNum, Long fileId, FileHandle fileHandle) throws LogException, DatabaseException { fileHandle=makeFileHandle(fileNum,FileMode.READ_MODE); this.hook464(fileId,fileHandle); return fileHandle; } protected FileHandle hook463( long fileNum, Long fileId, FileHandle fileHandle) throws LogException, DatabaseException { fileHandle=this.hook450(fileNum,fileId,fileHandle); return fileHandle; } protected void hook464( Long fileId, FileHandle fileHandle) throws LogException, DatabaseException { } protected void hook465( LogBuffer fullBuffer, long firstLsn, RandomAccessFile file) throws DatabaseException, ClosedChannelException, IOException { } protected void hook466( LogBuffer fullBuffer, long firstLsn, RandomAccessFile file, ByteBuffer data, IOException IOE) throws DatabaseException { throw new DatabaseException(IOE); } protected void hook467( boolean readOnly) throws DatabaseException { } }