docs: private long addBuffers(LogRecord logRecord
                              , boolean setMark
                              , boolean checkSpace
                              , boolean flush
                      )
                                      throws ObjectManagerException
  
      {
          final String methodName = "addBuffers";
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.entry(this,
                          cclass,
                          methodName,
                          new Object[] { logRecord,
                                        new Boolean(setMark),
                                        new Boolean(checkSpace),
                                        new Boolean(flush) });
  
          // Where we will put the data to be logged.
          int reservedAddress = 0;
          // The first and last pages in the logBuffer that we write into. 
          int startPage = 0;
          int endPage = 0;
          // The LogBuffers we will start and end filling.
          LogBuffer startFillingLogBuffer;
          LogBuffer endFillingLogBuffer;
          // LogSequenceNumber for return; 
          long returnLogSequenceNumber = 0;
  
          // Find the number of bytes we will write this time.
          int totalBytes = logRecord.getBytesLeft();
  
          // Decide if we need to split the log record into multiple parts. We do not allow long logRecords
          // to be written into the LogBuffer as a single piece because that would stop other log records 
          // from being written if they used up the whole of the log buffer. Additionally this would also mean that 
          // log records must be smaller than the log buffer in order to fit into it.
          boolean completed = false;
          if (totalBytes > maximumLogRecordPart) {
              // set the length to be written this time.
              totalBytes = maximumLogRecordPart;
          } else {
              completed = true;
          }
  
          long startWriteMilliseconds;
          if (gatherStatistics) {
              startWriteMilliseconds = System.currentTimeMillis();
          } // if (gatherStatistics). 
  
          // Lock the logBuffer so that we can work out where to copy this logRecord.
          synchronized (logBufferLock) {
              if (gatherStatistics) {
                  // Time how long we took to get the lock on the logBufferLock. 
                  long now = System.currentTimeMillis();
                  writeStalledMilliseconds += now - startWriteMilliseconds;
                  startWriteMilliseconds = now;
              } // if (gatherStatistics). 
  
              // Set address for the copy.
              reservedAddress = nextFreeByteInLogBuffer;
              // We never set nextFreeByteInLogBuffer to be the first byte, because this
              // is ultimately occupied by the sector bits copied from each sector. Hence there 
              // is no possibility of having to step over the sector byte.
              // if (nextFreeByteInLogBuffer%pageSize == 0) reservedAddress++;
              // startPage = reservedAddress/pageSize;
              startPage = lastPageFilling;
  
              // Calculate the number of extra page boundaries we will cross each time we start a new page, 
              // because we will need to leave a byte at the beginning of each one to put the sector bits in.
              // Work out pages started here rather than outside synchronize(logBufferLock) because we now know the
              // exact start address within the buffer. The -1 is to allow for the sector byte used in the first page, 
              // the pageSize-1 is the number of data bytes we expect to fit into each page. 
              int pagesStarted = (totalBytes + partHeaderLength + (reservedAddress % pageSize) - 1) / (pageSize - 1);
  
              // Move the start address on for the next log Record. This will leave us positioned
              // after byte zero, the sector byte but never on the sector byte.
              int updatedNextFreeByteInLogBuffer = reservedAddress + totalBytes + partHeaderLength + pagesStarted;
  
              // See if we have wrapped around the end of the logBuffer.
              boolean wrappedLogBuffer = false;
              // Capture the logBuffer where we will start to fill.
              startFillingLogBuffer = logBuffer;
  
              // Capture the current last page we notified, this may move forward while we do the check on the space 
              // in the LogBuffer, that is OK because we will fail to find the space and then come back here again.
              // This is updated by the NotifyHelper after it has cleared pageWritersStarted, so we must capture 
              // it before we advance pageWritersStarted. The notify helper cannot move this past lastPageFilling 
              // because we hold the logBufferLock and will not advance this before we release it.   
              // We only fill up to the nextToLastPageNotified otherwise an empty logBuffer and a full logBuffer
              // both have firstPageFilling == lastPageBNotified and so cannot be distinguished.
              int nextToLastPageNotified = lastPageNotified - 1;
              if (nextToLastPageNotified < 0)
                  nextToLastPageNotified = startFillingLogBuffer.numberOfPages - 1;
  
              if (updatedNextFreeByteInLogBuffer >= startFillingLogBuffer.buffer.length) {
                  updatedNextFreeByteInLogBuffer = updatedNextFreeByteInLogBuffer - startFillingLogBuffer.buffer.length;
                  wrappedLogBuffer = true;
              } // if (updatedNextFreeByteInLogBuffer >= logBuffer.length). 
              endPage = updatedNextFreeByteInLogBuffer / pageSize;
  
              // Make sure there is enough room left in the logBuffer and that we have not run into the pages that 
              // are still waiting to be written and notified. This prevents us from copying over over unwritten data.
              //
              // Take the two cases.
              //
              //    Buffer Wrapped Fill < Notify                  Buffer not wrapped Fill > Notify
              //           Fill        Notify                              Notify     Fill
              //    I//////            //////I                    I        ///////////         I
              //
              // The end page must not be in the unwritten hashed pages. We cannot run into the firstPageToNotify 
              // by padding the logBuffer because we only pad if a single page is filling. We always leave a one
              // page gap between the last full page and the lastPaageNotified, hence startPage is never equal
              // to nextToLastPageNotified.
              // We cannot test fillingLogBuffer.pageFlushPending[endPage] because we may be using a different log buffer
              // to the one the flushHelper is using.
              // This test assumes that a single LogRecord part cannot wrap round the entire buffer.
              if (((startPage < nextToLastPageNotified) && (endPage < startPage || endPage >= nextToLastPageNotified))
                  || ((startPage > nextToLastPageNotified) && (endPage < startPage && endPage >= nextToLastPageNotified))) {
                  if (gatherStatistics)
                      totalNumberOfThreadsFindingFullLogBuffers++;
                  // Request a larger logBuffer.
                  if (newLogBufferPages == 0
                      && startFillingLogBuffer.numberOfPages < maximumLogBufferPages)
                      newLogBufferPages = Math.min(startFillingLogBuffer.numberOfPages * 2,
                                                   maximumLogBufferPages);
  
                  // Go round again and see if the next attempt finds a logBuffer with enough space left.
                  if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
                      trace.exit(this,
                                 cclass,
                                 methodName,
                                 new Object[] { "logBuffer_full",
                                               new Integer(startPage),
                                               new Integer(endPage),
                                               new Integer(startFillingLogBuffer.pageWritersActive.get(startPage)),
                                               new Integer(nextToLastPageNotified) });
                  return -1;
              } // if(  (lastPageFilling < localLastPageNotified)...
  
              /*
               * Debug
               * // If we stepped into a new page the we must be the first writer.
               * if (logRecord.multiPartID != 0) {
               * if (logRecord.atStart()) {
               * // Preserve the start of any long active LogRecords.
               * multiPartFileStart[logRecord.multiPartID] = bufferFilePosition + reservedAddress;
               * multiPartSectorByte[logRecord.multiPartID] = filePositionSectorByte;
               * if (multiPartFileStart[logRecord.multiPartID] > fileLogHeader.fileSize) {
               * multiPartFileStart[logRecord.multiPartID] = multiPartFileStart[logRecord.multiPartID] - fileLogHeader.fileSize + FileLogHeader.headerLength * 2;
               * multiPartFileStart[logRecord.multiPartID] = (multiPartFileStart[logRecord.multiPartID] == 0 ? (byte) 1 : (byte) 0);
               * } // if (multiPartFileStart[logRecord.multiPartID] > fileLogHeader.fileSize).
               * // Capture unchecked bytes up to the start of this LogRecord,
               * // in case we have to truncate to this point.
               * multiPartUncheckedBytes[logRecord.multiPartID] = uncheckedBytes;
               * 
               * } else if (completed) {
               * multiPartFileStart[logRecord.multiPartID] = -1;
               * } // if (logRecord.atStart()).
               * } // if (logRecord.multiPartID != 0).
               * // Debug
               */
  
              // Were we asked to set the mark point?
              if (setMark) {
                  // Set Mark for first part only, later we truncate up to this point.
                  if (logRecord.atStart()) {
                      fileMark = bufferFilePosition + reservedAddress;
                      fileMarkSectorByte = bufferSectorByte;
                      uncheckedBytesUpToMarkPoint = uncheckedBytes;
  
                      if (fileMark > fileLogHeader.fileSize) {
                          fileMark = fileMark - fileLogHeader.fileSize + FileLogHeader.headerLength * 2;
                          fileMarkSectorByte = (fileMarkSectorByte == 0 ? (byte) 1 : (byte) 0);
                      } // if (fileMark > fileLogHeader.fileSize).
  
                      if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
                          trace.debug(this,
                                      cclass,
                                      methodName,
                                      new Object[] { "setMark",
                                                    new Long(fileMark),
                                                    new Byte(fileMarkSectorByte),
                                                    new Long(bufferFilePosition),
                                                    new Byte(bufferSectorByte) });
                  } // if (logRecord.atStart()).
  
                  // Include any long active log records. Find any log long Records that
                  // ended after the Mark point or are still being written. Move the mark point
                  // back to the earliest start.The actual start of the log file cannot be moved
                  // while we do this calculation because we hold the fileMarkLock so we use that
                  // as the reference point in the file to decide which LogRecord is the earliest
                  // in the active part of the file.
                  long fileMarkOffsetFromStart = offsetFromStart(fileMark);
                  long smallestFileMarkOffsetFromStart = fileMarkOffsetFromStart;
  
                  for (int i = 1; i < multiPartFileStart.length; i++) {
                      if (multiPartFileStart[i] >= 0) {
                          // Still writing.
                          long offsetFromStart = offsetFromStart(multiPartFileStart[i]);
                          if (offsetFromStart < smallestFileMarkOffsetFromStart) {
                              smallestFileMarkOffsetFromStart = offsetFromStart;
                              fileMark = multiPartFileStart[i];
                              fileMarkSectorByte = multiPartSectorByte[i];
                              uncheckedBytesUpToMarkPoint = multiPartUncheckedBytes[logRecord.multiPartID];
                          } // if (offsetFromStart < smallestFileMarkOffsetFromStart).
                      } // if (multiPartFileStart[i] >= 0).
                  } // for...
  
                  if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
                      trace.debug(this,
                                  cclass,
                                  methodName,
                                  new Object[] { "truncateMarkSet",
                                                new Long(fileMark),
                                                new Byte(fileMarkSectorByte),
                                                new Long(uncheckedBytesUpToMarkPoint) });
  
                  // Reset the unchecked bytes remaining after the mark point.
                  uncheckedBytes = uncheckedBytes - uncheckedBytesUpToMarkPoint;
              } // if (setMark).
  
              if (!checkSpace) {
                  // Unchecked bytes are not returned to the available space when we truncate the log file as they are already
                  // reserved and remain reserved after the truncate.
                  uncheckedBytes = uncheckedBytes + totalBytes + partHeaderLength;
              } // if (!checkSpace).    
  
              if (wrappedLogBuffer) {
                  startFillingLogBuffer.wrapped();
              } // if (wrappedLogBuffer).
                // Now capture the logBuffer we end filling, this may be the same as the start one if 
                // we did not wrap or we did not change the logBuffer.
              endFillingLogBuffer = logBuffer;
  
              if (completed) {
                  returnLogSequenceNumber = endFillingLogBuffer.sequenceNumberOfFirstPage + endPage;
                  logSequenceNumber = returnLogSequenceNumber;
                  if (flush)
                      endFillingLogBuffer.pageWaiterExists[endPage] = true;
              } // if(completed).
  
              // Count the threads who start writing in each page.
              startFillingLogBuffer.pageWritersActive.incrementAndGet(startPage);
  
              nextFreeByteInLogBuffer = updatedNextFreeByteInLogBuffer;
  
              // lastPageFilling is not volatile but checked first in the attempt to move firstPageFilling 
              // forward below. We have already incremented pageWritersStarted which is volatile and checked 
              // second.
              lastPageFilling = endPage;
  
              if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
                  trace.debug(this,
                              cclass,
                              methodName,
                              new Object[] { "logBuffer_reserved",
                                            new Long(endFillingLogBuffer.sequenceNumberOfFirstPage),
                                            new Integer(firstPageFilling),
                                            new Integer(lastPageFilling),
                                            new Integer(reservedAddress),
                                            new Integer(startPage),
                                            new Integer(endPage),
                                            new Integer(nextToLastPageNotified),
                                            new Integer(startFillingLogBuffer.pageWritersActive.get(startPage)) });
          } // synchronized (logBufferLock).
  
          // Lock on the logBuffer is now released, filling the buffer takes place in parallel.     
  
          // Calculation of BytesInLogBuffer above means we will already allowed for any leading byte of sector bits. 
          // Add a header for the logRecord part followed by the logRecord data. 
          addPart(logRecord,
                  startFillingLogBuffer.buffer,
                  completed,
                  reservedAddress,
                  totalBytes);
  
          if (gatherStatistics) {
              // Time how long we took to complete copying the logRecord. 
              long now = System.currentTimeMillis();
              writeCopyingMilliseconds += now - startWriteMilliseconds;
              startWriteMilliseconds = now;
          } // if (gatherStatistics). 
  
          // Mark our write as finished and restart the flushHelper if it has stalled.
          boolean startFlush = false;
          for (int iPage = startPage;; iPage++) {
              if (iPage == startFillingLogBuffer.numberOfPages) {
                  iPage = 0;
                  startFillingLogBuffer = logBuffer;
              } // if (iPage == fillingLogBuffer.numberOfPages).  
  
              synchronized (startFillingLogBuffer.pageStateLock[iPage]) {
                  // We can loop through the logBuffer multiple times if the flushHelper writes pages
                  // while we are in this for loop, and enables writer threads to keep advancing the 
                  // lastPageFilling. On the first pass through the loop startFlush is false so we use 
                  // that to determine if we are on the startPage rather than checking if (iPage == startPage)
                  // as that would be true on each successive pass through the pages.
                  if (!startFlush)
                      startFillingLogBuffer.pageWritersActive.decrementAndGet(startPage);
  
                  // lastPageFilling may have no data in it as yet. It is never completely full either 
                  // as we would move to the next page in that case, so there is never any possibility of it being ready 
                  // to write without padding it.
                  if (iPage == lastPageFilling)
                      break;
  
                  // pageWritersStarted is no longer incrementing because iPage is positioned before lastPageFilling. 
                  if (startFillingLogBuffer.pageWritersActive.get(iPage) == 0
                      && iPage == firstPageFilling) {
                      startFillingLogBuffer.pageFlushPending[iPage] = true;
                      // Advance firstPageFilling in a way that is always valid because it is captured in flush()
                      // above without holding a pageStateLock to protect it.
                      if (firstPageFilling + 1 == startFillingLogBuffer.numberOfPages)
                          firstPageFilling = 0;
                      else
                          firstPageFilling++;
  
                      startFlush = true;
                  } else {
                      break;
                  } // if (fillingLogBuffer.pageWriterStarted...
              } // synchronized (fillingLogBuffer.pageStateLock[i]).
          } // for...       
  
          if (gatherStatistics) {
              // Time how long we took to update the logBuffer state.
              long now = System.currentTimeMillis();
              writeUpdateStateMilliseconds += now - startWriteMilliseconds;
              startWriteMilliseconds = now;
          } // if (gatherStatistics). 
  
          if (Tracing.isAnyTracingEnabled() && trace.isDebugEnabled())
              trace.debug(this,
                          cclass,
                          methodName,
                          new Object[] { "firstPageFilling update done",
                                        new Integer(firstPageFilling),
                                        new Integer(lastPageFilling),
                                        new Integer(startPage),
                                        new Integer(endPage),
                                        new Integer(startFillingLogBuffer.pageWritersActive.get(startPage)),
                                        new Boolean(startFillingLogBuffer.pageFlushPending[startPage]) });
          if (startFlush)
              flushHelper.startFlush();
  
          // See if there is anything to flush.
          if (flush && completed) {
              endFillingLogBuffer.waitForFlush(endPage);
          }
  
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.exit(this,
                         cclass,
                         methodName,
                         new Object[] { new Long(returnLogSequenceNumber) });
          return returnLogSequenceNumber;
      }