--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.log.LogAccessFile\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.store.raw.log;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.io.StorageRandomAccessFile;\r
+\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.SyncFailedException;\r
+import java.io.InterruptedIOException;\r
+import java.util.LinkedList;\r
+\r
+import org.apache.derby.iapi.services.io.FormatIdOutputStream;\r
+import org.apache.derby.iapi.services.io.ArrayOutputStream;\r
+import org.apache.derby.iapi.store.raw.RawStoreFactory;\r
+\r
+\r
+/**\r
+ Wraps a RandomAccessFile file to provide buffering\r
+ on log writes. Only supports the write calls\r
+ required for the log!\r
+\r
+ MT - unsafe. Caller of this class must provide synchronization. The one\r
+ exception is with the log file access, LogAccessFile will touch the log\r
+ only inside synchronized block protected by the semaphore, which is\r
+ defined by the creator of this object.\r
+ \r
+ Write to the log buffers are allowed when there are free buffers even\r
+ when dirty buffers are being written(flushed) to the disk by a different\r
+ thread. Only one flush writes to log file at a time, other wait for it to finish.\r
+\r
+ Except for flushLogAccessFile , SyncAccessLogFile other function callers\r
+ must provide syncronization that will allow only one of them to write to \r
+ the buffers. \r
+\r
+ Log Buffers are used in circular fashion, each buffer moves through following stages: \r
+ freeBuffers --> dirtyBuffers --> freeBuffers. Movement of buffers from one\r
+ stage to another stage is synchronized using the object(this) of this class. \r
+\r
+ A Checksum log record that has the checksum value for the data that is\r
+ being written to the disk is generated and written before the actual data. \r
+ Except for the large log records that does not fit into a single buffer, \r
+ checksum is calcualted for a group of log records that are in the buffer \r
+ when buffers is switched. Checksum log record is written into the reserved\r
+ space in the beginning buffer. \r
+\r
+ In case of a large log record that does not fit into a bufffer, it needs to \r
+ be written directly to the disk instead of going through the log buffers. \r
+ In this case the log record write gets broken into three parts:\r
+ 1) Write checksum log record and LOG RECORD HEADER (length + instant) \r
+ 2) Write the log record. \r
+ 3) Write the trailing length of the log record. \r
+\r
+ Checksum log records helps in identifying the incomplete log disk writes during \r
+ recovery. This is done by recalculating the checksum value for the data on\r
+ the disk and comparing it to the the value stored in the checksum log\r
+ record. \r
+\r
+*/\r
+public class LogAccessFile \r
+{\r
+\r
+ /**\r
+ * The fixed size of a log record is 16 bytes:\r
+ * int length : 4 bytes\r
+ * long instant : 8 bytes\r
+ * int trailing length : 4 bytes\r
+ **/\r
+ private static final int LOG_RECORD_FIXED_OVERHEAD_SIZE = 16;\r
+ private static final int LOG_RECORD_HEADER_SIZE = 12; //(length + instant)\r
+ private static final int LOG_RECORD_TRAILER_SIZE = 4; //trailing length \r
+ private static final int LOG_NUMBER_LOG_BUFFERS = 3;\r
+\r
+\r
+ private LinkedList freeBuffers; //list of free buffers\r
+ private LinkedList dirtyBuffers; //list of dirty buffers to flush\r
+ private LogAccessFileBuffer currentBuffer; //current active buffer\r
+ private boolean flushInProgress = false;\r
+ \r
+ private final StorageRandomAccessFile log;\r
+\r
+ // log can be touched only inside synchronized block protected by\r
+ // logFileSemaphore.\r
+ private final Object logFileSemaphore;\r
+\r
+ static int mon_numWritesToLog;\r
+ static int mon_numBytesToLog;\r
+\r
+\r
+ //streams used to generated check sume log record ; see if there is any simpler way\r
+ private ArrayOutputStream logOutputBuffer;\r
+ private FormatIdOutputStream logicalOut;\r
+ private boolean directWrite = false; //true when log is written directly to file.\r
+ private long checksumInstant = -1;\r
+ private int checksumLength;\r
+ private int checksumLogRecordSize; //checksumLength + LOG_RECORD_FIXED_OVERHEAD_SIZE\r
+ private boolean writeChecksum; \r
+ private ChecksumOperation checksumLogOperation;\r
+ private LogRecord checksumLogRecord;\r
+ private LogToFile logFactory;\r
+ private boolean databaseEncrypted=false;\r
+ \r
+ public LogAccessFile(LogToFile logFactory,\r
+ StorageRandomAccessFile log, \r
+ int bufferSize) \r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if(SanityManager.DEBUG_ON("LogBufferOff"))\r
+ bufferSize = 10; // make it very tiny\r
+ }\r
+ \r
+ this.log = log;\r
+ logFileSemaphore = log;\r
+ this.logFactory = logFactory;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(LOG_NUMBER_LOG_BUFFERS >= 1);\r
+ \r
+ //initialize buffers lists\r
+ freeBuffers = new LinkedList();\r
+ dirtyBuffers = new LinkedList();\r
+\r
+\r
+ //add all buffers to free list\r
+ for (int i = 0; i < LOG_NUMBER_LOG_BUFFERS; i++)\r
+ {\r
+ LogAccessFileBuffer b = new LogAccessFileBuffer(bufferSize);\r
+ freeBuffers.addLast(b);\r
+ }\r
+\r
+ currentBuffer = (LogAccessFileBuffer) freeBuffers.removeFirst();\r
+ \r
+ // Support for Transaction Log Checksum in Derby was added in 10.1\r
+ // Check to see if the Store have been upgraded to 10.1 or later before\r
+ // writing the checksum log records. Otherwise recovery will fail\r
+ // incase user tries to revert back to versions before 10.1 in \r
+ // soft upgrade mode. \r
+ writeChecksum = logFactory.checkVersion(RawStoreFactory.DERBY_STORE_MAJOR_VERSION_10, \r
+ RawStoreFactory.DERBY_STORE_MINOR_VERSION_1);\r
+ if(writeChecksum)\r
+ {\r
+ /**\r
+ * setup structures that are required to write the checksum log records\r
+ * for a group of log records are being written to the disk. \r
+ */\r
+ checksumLogOperation = new ChecksumOperation();\r
+ checksumLogOperation.init();\r
+ checksumLogRecord = new LogRecord();\r
+\r
+ // Note: Checksum log records are not related any particular transaction, \r
+ // they are written to store a checksum information identify\r
+ // incomplete log record writes. No transacton id is set for this\r
+ // log record. That is why a null argument is passed below \r
+ // setValue(..) call. \r
+ checksumLogRecord.setValue(null, checksumLogOperation);\r
+\r
+ checksumLength = \r
+ checksumLogRecord.getStoredSize(checksumLogOperation.group(), null) + \r
+ checksumLogOperation.getStoredSize();\r
+\r
+ // calculate checksum log operation length when the database is encrypted\r
+ if (logFactory.databaseEncrypted())\r
+ {\r
+ checksumLength = logFactory.getEncryptedDataLength(checksumLength);\r
+ databaseEncrypted = true;\r
+ }\r
+ checksumLogRecordSize = checksumLength + LOG_RECORD_FIXED_OVERHEAD_SIZE;\r
+\r
+ //streams required to convert a log record to raw byte array. \r
+ logOutputBuffer = new ArrayOutputStream(); \r
+ logicalOut = new FormatIdOutputStream(logOutputBuffer);\r
+\r
+ /** initialize the buffer with space reserved for checksum log record in\r
+ * the beginning of the log buffer; checksum record is written into\r
+ * this space when buffer is switched or while doing direct write to the log file.\r
+ */\r
+ }else\r
+ {\r
+ //checksumming of transaction log feature is not in use. \r
+ checksumLogRecordSize = 0;\r
+ }\r
+ \r
+ currentBuffer.init(checksumLogRecordSize);\r
+ }\r
+\r
+\r
+ private byte[] db = new byte[LOG_RECORD_TRAILER_SIZE]; \r
+\r
+\r
+ /**\r
+ * Write a single log record to the stream.\r
+ * <p>\r
+ * For performance pass all parameters rather into a specialized routine\r
+ * rather than maintaining the writeInt, writeLong, and write interfaces\r
+ * that this class provides as a standard OutputStream. It will make it\r
+ * harder to use other OutputStream implementations, but makes for less\r
+ * function calls and allows optimizations knowing when to switch buffers.\r
+ * <p>\r
+ * This routine handles all log records which are smaller than one log\r
+ * buffer. If a log record is bigger than a log buffer it calls\r
+ * writeUnbufferedLogRecord().\r
+ * <p>\r
+ * The log record written will always look the same as if the following\r
+ * code had been executed:\r
+ * writeInt(length)\r
+ * writeLong(instant)\r
+ * write(data, data_offset, (length - optional_data_length) )\r
+ *\r
+ * if (optional_data_length != 0)\r
+ * write(optional_data, optional_data_offset, optional_data_length)\r
+ *\r
+ * writeInt(length)\r
+ *\r
+ * @param length (data + optional_data) length bytes to write\r
+ * @param instant the log address of this log record.\r
+ * @param data "from" array to copy "data" portion of rec\r
+ * @param data_offset offset in "data" to start copying from.\r
+ * @param optional_data "from" array to copy "optional data" from\r
+ * @param optional_data_offset offset in "optional_data" to start copy from\r
+ * @param optional_data_length length of optional data to copy.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public void writeLogRecord(\r
+ int length,\r
+ long instant,\r
+ byte[] data,\r
+ int data_offset,\r
+ byte[] optional_data,\r
+ int optional_data_offset,\r
+ int optional_data_length)\r
+ throws StandardException, IOException \r
+ {\r
+ int total_log_record_length = length + LOG_RECORD_FIXED_OVERHEAD_SIZE;\r
+\r
+ if (total_log_record_length <= currentBuffer.bytes_free)\r
+ {\r
+ byte[] b = currentBuffer.buffer;\r
+ int p = currentBuffer.position;\r
+\r
+ // writeInt(length)\r
+ p = writeInt(length, b, p);\r
+ \r
+ // writeLong(instant)\r
+ p = writeLong(instant, b , p);\r
+\r
+ // write(data, data_offset, length - optional_data_length)\r
+ int transfer_length = (length - optional_data_length);\r
+ System.arraycopy(data, data_offset, b, p, transfer_length);\r
+\r
+ p += transfer_length;\r
+\r
+ if (optional_data_length != 0)\r
+ {\r
+ // write(\r
+ // optional_data, optional_data_offset, optional_data_length);\r
+\r
+ System.arraycopy(\r
+ optional_data, optional_data_offset, \r
+ b, p, \r
+ optional_data_length);\r
+\r
+ p += optional_data_length;\r
+ }\r
+\r
+ // writeInt(length)\r
+ p = writeInt(length, b, p);\r
+ \r
+ currentBuffer.position = p;\r
+ currentBuffer.bytes_free -= total_log_record_length;\r
+ }\r
+ else\r
+ {\r
+ \r
+ /** Because current log record will never fit in a single buffer\r
+ * a direct write to the log file is required instead of \r
+ * writing the log record through the log bufffers. \r
+ */\r
+ directWrite = true;\r
+\r
+ byte[] b = currentBuffer.buffer;\r
+ int p = currentBuffer.position;\r
+\r
+ // writeInt(length)\r
+ p = writeInt(length , b, p);\r
+ \r
+ // writeLong(instant)\r
+ p = writeLong(instant, b, p);\r
+\r
+ currentBuffer.position = p;\r
+ currentBuffer.bytes_free -= LOG_RECORD_HEADER_SIZE;\r
+\r
+ /** using a seperate small buffer to write the traling length\r
+ * instead of the log buffer because data portion will be \r
+ * written directly to log file after the log buffer is \r
+ * flushed and the trailing length should be written after that. \r
+ */\r
+\r
+ // writeInt(length)\r
+ writeInt(length , db, 0);\r
+\r
+ if(writeChecksum)\r
+ {\r
+ checksumLogOperation.reset();\r
+ checksumLogOperation.update(b, checksumLogRecordSize, p - checksumLogRecordSize);\r
+ checksumLogOperation.update(data, data_offset, length - optional_data_length);\r
+ if (optional_data_length != 0)\r
+ {\r
+ checksumLogOperation.update(optional_data, optional_data_offset, optional_data_length); \r
+ }\r
+\r
+ // update the checksum to include the trailing length.\r
+ checksumLogOperation.update(db, 0, LOG_RECORD_TRAILER_SIZE);\r
+ \r
+ // write checksum log record to the log buffer \r
+ writeChecksumLogRecord();\r
+ }\r
+ \r
+ \r
+ // now do the writes directly to the log file. \r
+\r
+ // flush all buffers before wrting directly to the log file. \r
+ flushLogAccessFile();\r
+\r
+ // Note:No Special Synchronization required here , \r
+ // There will be nothing to write by flushDirtyBuffers that can run\r
+ // in parallel to the threads that is executing this code. Above\r
+ // flush call should have written all the buffers and NO new log will \r
+ // get added until the following direct log to file call finishes. \r
+\r
+\r
+ // write the rest of the log directltly to the log file. \r
+ writeToLog(data, data_offset, length - optional_data_length);\r
+ if (optional_data_length != 0)\r
+ {\r
+ writeToLog(\r
+ optional_data, optional_data_offset, optional_data_length);\r
+ }\r
+\r
+ // write the trailing length \r
+ writeToLog(db,0, 4);\r
+ directWrite = false;\r
+ }\r
+ }\r
+\r
+\r
+\r
+ private final int writeInt(int i , byte b[], int p)\r
+ {\r
+ \r
+ b[p++] = (byte) ((i >>> 24) & 0xff); \r
+ b[p++] = (byte) ((i >>> 16) & 0xff); \r
+ b[p++] = (byte) ((i >>> 8) & 0xff); \r
+ b[p++] = (byte) (i & 0xff); \r
+ return p;\r
+ }\r
+\r
+\r
+ private final int writeLong(long l , byte b[], int p)\r
+ {\r
+ b[p++] = (byte) (((int)(l >>> 56)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 48)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 40)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 32)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 24)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 16)) & 0xff); \r
+ b[p++] = (byte) (((int)(l >>> 8)) & 0xff); \r
+ b[p++] = (byte) (((int)l) & 0xff); \r
+ return p;\r
+ }\r
+\r
+ public void writeInt(int i) \r
+ {\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentBuffer.bytes_free >= 4);\r
+ }\r
+ \r
+ currentBuffer.position = \r
+ writeInt(i , currentBuffer.buffer, currentBuffer.position);\r
+ currentBuffer.bytes_free -= 4;\r
+ }\r
+\r
+ public void writeLong(long l) \r
+ {\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentBuffer.bytes_free >= 8);\r
+ }\r
+ \r
+ currentBuffer.position = \r
+ writeLong(l , currentBuffer.buffer, currentBuffer.position);\r
+ currentBuffer.bytes_free -= 8;\r
+ }\r
+\r
+ public void write(int b) \r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentBuffer.bytes_free > 0);\r
+ }\r
+ \r
+ currentBuffer.buffer[currentBuffer.position++] = (byte) b;\r
+ currentBuffer.bytes_free--;\r
+ }\r
+\r
+\r
+ public void write(byte b[], int off, int len) \r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(len <= currentBuffer.bytes_free);\r
+ }\r
+ \r
+ System.arraycopy(b, off, currentBuffer.buffer, currentBuffer.position, len);\r
+ currentBuffer.bytes_free -= len;\r
+ currentBuffer.position += len;\r
+ }\r
+\r
+\r
+ /**\r
+ * Write data from all dirty buffers into the log file.\r
+ * <p>\r
+ * A call for clients of LogAccessFile to insure that all privately buffered\r
+ * data has been writen to the file - so that reads on the file using one\r
+ * of the various scan classes will see\r
+ * all the data which has been writen to this point.\r
+ * <p>\r
+ * Note that this routine only "writes" the data to the file, this does not\r
+ * mean that the data has been synced to disk unless file was opened in\r
+ * WRITE SYNC mode(rws/rwd). The only way to insure that is by calling\r
+ * is to call syncLogAccessFile() after this call in Non-WRITE sync mode(rw)\r
+ * \r
+ * <p>\r
+ * MT-Safe : parallel thereads can call this function, only one threads does\r
+ * the flush and the other threads waits for the one that is doing the flush to finish.\r
+ * Currently there are two possible threads that can call this function in parallel \r
+ * 1) A Thread that is doing the commit\r
+ * 2) A Thread that is writing to the log and log buffers are full or\r
+ * a log records does not fit in a buffer. (Log Buffers\r
+ * full(switchLogBuffer() or a log record size that is greater than\r
+ * logbuffer size has to be writtern through writeToLog call directlty)\r
+ * Note: writeToLog() is not synchronized on the semaphore\r
+ * that is used to do buffer management to allow writes \r
+ * to the free buffers when flush is in progress. \r
+ **/\r
+ protected void flushDirtyBuffers() throws IOException \r
+ {\r
+ LogAccessFileBuffer buf = null;\r
+ int noOfBuffers;\r
+ int nFlushed= 0;\r
+ try{\r
+ synchronized(this)\r
+ {\r
+ /**if some one else flushing wait, otherwise it is possible \r
+ * different threads will get different buffers and order can \r
+ * not be determined.\r
+ * \r
+ **/\r
+ while(flushInProgress)\r
+ {\r
+ try{\r
+ wait();\r
+ }catch (InterruptedException ie) \r
+ {\r
+ //do nothing, let the flush request to complete.\r
+ //because it possible that other thread which is\r
+ //currently might have completed this request also ,\r
+ //if exited on interrupt and throw exception, can not\r
+ //be sure whether this transaction is COMMITTED ot not.\r
+ }\r
+ }\r
+ \r
+ noOfBuffers = dirtyBuffers.size();\r
+ if(noOfBuffers > 0)\r
+ buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst();\r
+ \r
+ flushInProgress = true;\r
+ }\r
+ \r
+ while(nFlushed < noOfBuffers)\r
+ {\r
+ if (buf.position != 0)\r
+ writeToLog(buf.buffer, 0, buf.position);\r
+\r
+ nFlushed++;\r
+ synchronized(this)\r
+ {\r
+ //add the buffer that was written previosly to the free list\r
+ freeBuffers.addLast(buf);\r
+ if(nFlushed < noOfBuffers)\r
+ buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst();\r
+ else\r
+ {\r
+ //see if we can flush more, that came when we are at it.\r
+ //don't flush more than the total number of buffers,\r
+ //that might lead to starvation of the current thread.\r
+ int size = dirtyBuffers.size();\r
+ if(size > 0 && nFlushed <= LOG_NUMBER_LOG_BUFFERS)\r
+ {\r
+ noOfBuffers += size;\r
+ buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ \r
+ }finally{\r
+ synchronized(this)\r
+ {\r
+ flushInProgress = false;\r
+ notifyAll();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ //flush all the the dirty buffers to disk\r
+ public void flushLogAccessFile() throws IOException, StandardException \r
+ {\r
+ switchLogBuffer();\r
+ flushDirtyBuffers();\r
+ }\r
+\r
+ \r
+ /**\r
+ * Appends the current Buffer to the dirty Buffer list and assigns a free\r
+ * buffer to be the currrent active buffer . Flushing of the buffer\r
+ * to disk is delayed if there is a free buffer available. \r
+ * dirty buffers will be flushed to the disk \r
+ * when flushDirtyBuffers() is invoked by a commit call \r
+ * or when no more free buffers are available. \r
+ */\r
+ public void switchLogBuffer() throws IOException, StandardException \r
+ {\r
+\r
+ synchronized(this)\r
+ {\r
+ // ignore empty buffer switch requests\r
+ if(currentBuffer.position == checksumLogRecordSize)\r
+ return;\r
+\r
+ // calculate the checksum for the current log buffer \r
+ // and write the record to the space reserverd in \r
+ // the beginning of the buffer. \r
+ if(writeChecksum && !directWrite)\r
+ {\r
+ checksumLogOperation.reset();\r
+ checksumLogOperation.update(currentBuffer.buffer, checksumLogRecordSize, currentBuffer.position - checksumLogRecordSize);\r
+ writeChecksumLogRecord();\r
+ }\r
+\r
+ //add the current buffer to the flush buffer list\r
+ dirtyBuffers.addLast(currentBuffer);\r
+\r
+ //if there is No free buffer, flush the buffers to get a free one \r
+ if(freeBuffers.size() == 0) \r
+ {\r
+ flushDirtyBuffers();\r
+ //after the flush call there should be a free buffer\r
+ //because this is only methods removes items from \r
+ //free buffers and removal is in synchronized block. \r
+ }\r
+\r
+\r
+ // there should be free buffer available at this point.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(freeBuffers.size() > 0);\r
+\r
+ //switch over to the next log buffer, let someone else write it.\r
+ currentBuffer = (LogAccessFileBuffer) freeBuffers.removeFirst();\r
+ currentBuffer.init(checksumLogRecordSize);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentBuffer.position == checksumLogRecordSize);\r
+ SanityManager.ASSERT(\r
+ currentBuffer.bytes_free == currentBuffer.length);\r
+ SanityManager.ASSERT(currentBuffer.bytes_free > 0);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Guarantee all writes up to the last call to flushLogAccessFile on disk.\r
+ * <p>\r
+ * A call for clients of LogAccessFile to insure that all data written\r
+ * up to the last call to flushLogAccessFile() are written to disk.\r
+ * This call will not return until those writes have hit disk.\r
+ * <p>\r
+ * Note that this routine may block waiting for I/O to complete so \r
+ * callers should limit the number of resource held locked while this\r
+ * operation is called. It is expected that the caller\r
+ * Note that this routine only "writes" the data to the file, this does not\r
+ * mean that the data has been synced to disk. The only way to insure that\r
+ * is to first call switchLogBuffer() and then follow by a call of sync().\r
+ *\r
+ **/\r
+ public void syncLogAccessFile() \r
+ throws IOException, StandardException\r
+ {\r
+ for( int i=0; ; )\r
+ {\r
+ // 3311: JVM sync call sometimes fails under high load against NFS \r
+ // mounted disk. We re-try to do this 20 times.\r
+ try\r
+ {\r
+ synchronized( this)\r
+ {\r
+ log.sync( false);\r
+ }\r
+\r
+ // the sync succeed, so return\r
+ break;\r
+ }\r
+ catch( SyncFailedException sfe )\r
+ {\r
+ i++;\r
+ try\r
+ {\r
+ // wait for .2 of a second, hopefully I/O is done by now\r
+ // we wait a max of 4 seconds before we give up\r
+ Thread.sleep( 200 ); \r
+ }\r
+ catch( InterruptedException ie )\r
+ { //does not matter weather I get interrupted or not\r
+ }\r
+\r
+ if( i > 20 )\r
+ throw StandardException.newException(\r
+ SQLState.LOG_FULL, sfe);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ The database is being marked corrupted, get rid of file pointer without\r
+ writing out anything more.\r
+ */\r
+ public void corrupt() throws IOException\r
+ {\r
+ synchronized(logFileSemaphore)\r
+ {\r
+ if (log != null)\r
+ log.close();\r
+ }\r
+ }\r
+\r
+ public void close() throws IOException, StandardException\r
+ {\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (currentBuffer.position != checksumLogRecordSize)\r
+ SanityManager.THROWASSERT(\r
+ "Log file being closed with data still buffered " + \r
+ currentBuffer.position + " " + currentBuffer.bytes_free);\r
+ }\r
+\r
+ flushLogAccessFile();\r
+\r
+ synchronized(logFileSemaphore)\r
+ {\r
+ if (log != null)\r
+ log.close();\r
+ }\r
+ }\r
+\r
+\r
+ /* write to the log file */\r
+ private void writeToLog(byte b[], int off, int len) throws IOException\r
+ {\r
+ synchronized(logFileSemaphore)\r
+ {\r
+ if (log != null)\r
+ {\r
+\r
+ // Try to handle case where user application is throwing\r
+ // random interrupts at Derby threads, retry in the case\r
+ // of IO exceptions 5 times. After that hope that it is \r
+ // a real disk problem - an IO error in a write to the log file\r
+ // is going to take down the whole system, so seems worthwhile\r
+ // to retry.\r
+ for (int i = 0; ;i++)\r
+ {\r
+ try \r
+ {\r
+ log.write(b, off, len);\r
+ break;\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ // just fall through and rety the log write 1st 5 times.\r
+\r
+ if (i >= 5)\r
+ throw ioe;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ mon_numWritesToLog++;\r
+ mon_numBytesToLog += len;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * reserve the space for the checksum log record in the log file. \r
+ *\r
+ * @param length the length of the log record to be written\r
+ * @param logFileNumber current log file number \r
+ * @param currentPosition current position in the log file. \r
+ *\r
+ * @return the space that is needed to write a checksum log record.\r
+ */\r
+ protected long reserveSpaceForChecksum(int length, long logFileNumber, long currentPosition )\r
+ throws StandardException, IOException \r
+ {\r
+\r
+ int total_log_record_length = length + LOG_RECORD_FIXED_OVERHEAD_SIZE;\r
+ boolean reserveChecksumSpace = false;\r
+ \r
+ /* checksum log record is calculated for a group of log \r
+ * records that can fit in to a single buffer or for \r
+ * a single record when it does not fit into \r
+ * a fit into a buffer at all. When a new buffer \r
+ * is required to write a log record, log space \r
+ * has to be reserved before writing the log record\r
+ * becuase checksum is written in the before the \r
+ * log records that are being checksummed. \r
+ * What it also means is a real log instant has to be \r
+ * reserved for writing the checksum log record in addition \r
+ * to the log buffer space.\r
+ */\r
+ \r
+\r
+ /* reserve checkum space for new log records if a log buffer switch had\r
+ * happened before because of a explicit log flush requests(like commit)\r
+ * or a long record write \r
+ */\r
+ if(currentBuffer.position == checksumLogRecordSize)\r
+ {\r
+ // reserver space if log checksum feature is enabled.\r
+ reserveChecksumSpace = writeChecksum;\r
+ }\r
+ else{\r
+ if (total_log_record_length > currentBuffer.bytes_free)\r
+ {\r
+ // the log record that is going to be written is not \r
+ // going to fit in the current buffer, switch the \r
+ // log buffer to create buffer space for it. \r
+ switchLogBuffer();\r
+ // reserve space if log checksum feature is enabled. \r
+ reserveChecksumSpace = writeChecksum;\r
+ }\r
+ }\r
+ \r
+ if(reserveChecksumSpace)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // Prevoiusly reserved real checksum instant should have been\r
+ // used, before an another one is generated. \r
+ SanityManager.ASSERT(checksumInstant == -1, "CHECKSUM INSTANT IS GETTING OVER WRITTEN");\r
+ }\r
+ \r
+ checksumInstant = LogCounter.makeLogInstantAsLong(logFileNumber, currentPosition);\r
+ return checksumLogRecordSize;\r
+ }else\r
+ {\r
+ return 0 ;\r
+ }\r
+ }\r
+\r
+\r
+ /*\r
+ * generate the checkum log record and write it into the log buffer.\r
+ */\r
+ private void writeChecksumLogRecord() throws IOException, StandardException\r
+ {\r
+ \r
+ byte[] b = currentBuffer.buffer;\r
+ int p = 0; //checksum is written in the beginning of the buffer\r
+\r
+ // writeInt(length)\r
+ p = writeInt(checksumLength, b , p);\r
+ \r
+ // writeLong(instant)\r
+ p = writeLong(checksumInstant, b , p);\r
+\r
+ //write the checksum log operation \r
+ logOutputBuffer.setData(b);\r
+ logOutputBuffer.setPosition(p);\r
+ logicalOut.writeObject(checksumLogRecord);\r
+\r
+ if(databaseEncrypted)\r
+ {\r
+ //encrypt the checksum log operation part.\r
+ int len = \r
+ logFactory.encrypt(b, LOG_RECORD_HEADER_SIZE, checksumLength, \r
+ b, LOG_RECORD_HEADER_SIZE);\r
+ \r
+ \r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(len == checksumLength, \r
+ "encrypted log buffer length != log buffer len");\r
+ }\r
+\r
+ p = LOG_RECORD_HEADER_SIZE + checksumLength ;\r
+\r
+ // writeInt(length) trailing\r
+ p = writeInt(checksumLength, b, p );\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(p == checksumLogRecordSize, "position=" + p + "ckrecordsize=" + checksumLogRecordSize);\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(\r
+ LogToFile.DBG_FLAG, \r
+ "Write log record: tranId=Null" +\r
+ " instant: " + LogCounter.toDebugString(checksumInstant) + " length: " +\r
+ checksumLength + "\n" + checksumLogOperation + "\n");\r
+ }\r
+ checksumInstant = -1; \r
+ }\r
+\r
+ }\r
+\r
+\r
+ protected void writeEndMarker(int marker) throws IOException, StandardException \r
+ {\r
+ //flush all the buffers and then write the end marker.\r
+ flushLogAccessFile();\r
+ \r
+ byte[] b = currentBuffer.buffer;\r
+ int p = 0; //end is written in the beginning of the buffer, no\r
+ //need to checksum a int write.\r
+ p = writeInt(marker , b , p);\r
+ writeToLog(b, 0, p);\r
+ }\r
+\r
+ \r
+}\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r