You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2008/04/11 14:30:08 UTC

svn commit: r647139 - in /db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data: FileContainer.java InputStreamContainer.java RAFContainer.java RAFContainer4.java

Author: kahatlen
Date: Fri Apr 11 05:30:06 2008
New Revision: 647139

URL: http://svn.apache.org/viewvc?rev=647139&view=rev
Log:
DERBY-3347: ERROR XSDB3: Container information cannot change once written

On JVMs that support the NIO API, use NIO consistently when accessing
the data files. Mixing old style I/O and NIO has caused problems on
some platforms since thread-safety is only guaranteed if all access
happens through the FileChannel interface.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/FileContainer.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/InputStreamContainer.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer4.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/FileContainer.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/FileContainer.java?rev=647139&r1=647138&r2=647139&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/FileContainer.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/FileContainer.java Fri Apr 11 05:30:06 2008
@@ -26,19 +26,11 @@
 import org.apache.derby.iapi.reference.Limits;
 import org.apache.derby.iapi.reference.SQLState;
 
-import org.apache.derby.impl.store.raw.data.BaseContainer;
-import org.apache.derby.impl.store.raw.data.BaseContainerHandle;
-import org.apache.derby.impl.store.raw.data.BasePage;
-import org.apache.derby.impl.store.raw.data.PageVersion;
-
 import org.apache.derby.iapi.services.cache.Cacheable;
 import org.apache.derby.iapi.services.cache.CacheManager;
 import org.apache.derby.iapi.services.context.ContextService;
-import org.apache.derby.iapi.services.daemon.DaemonService;
-import org.apache.derby.iapi.services.daemon.Serviceable;
 import org.apache.derby.iapi.services.monitor.Monitor;
 import org.apache.derby.iapi.services.sanity.SanityManager;
-import org.apache.derby.iapi.services.io.FormatIdUtil;
 import org.apache.derby.iapi.services.io.FormatIdOutputStream;
 import org.apache.derby.iapi.services.io.StoredFormatIds;
 import org.apache.derby.iapi.services.io.TypedFormat;
@@ -46,11 +38,8 @@
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.store.raw.ContainerHandle;
 import org.apache.derby.iapi.store.raw.ContainerKey;
-import org.apache.derby.iapi.store.raw.LockingPolicy;
-import org.apache.derby.iapi.store.raw.Loggable;
 import org.apache.derby.iapi.store.raw.Page;
 import org.apache.derby.iapi.store.raw.PageKey;
-import org.apache.derby.iapi.store.raw.PageTimeStamp;
 import org.apache.derby.iapi.store.raw.RecordHandle;
 import org.apache.derby.iapi.store.raw.RawStoreFactory;
 import org.apache.derby.iapi.store.raw.Transaction;
@@ -69,11 +58,11 @@
 
 import java.io.IOException;
 import java.io.DataInput;
-import java.io.DataOutput;
 
 import java.util.Properties;
 import java.util.zip.CRC32;
 
+import org.apache.derby.io.StorageRandomAccessFile;
 
 /**
 	FileContainer is an abstract base class for containers
@@ -692,39 +681,33 @@
     }
 
 	/**
-		Read the container's header.  Assumes the input stream (fileData)
-		is positioned at the beginning of the file.
+        Read the container's header.
 
-		Subclass that implements openContainer is expected to manufacture a DataInput 
-		stream which is used here to read the header.
+        When this method is called, the embryonic page that is passed in must
+        have been read directly from the file or the input stream, even if the
+        alloc page may still be in cache.  This is because a stubbify operation
+        only writes the stub to disk, it does not get rid of any stale page
+        from the page cache.  So if it so happens that the stubbified container
+        object is aged out of the container cache but the first alloc page
+        hasn't, then when any stale page of this container wants to be written
+        out, the container needs to be reopened, which is when this routine is
+        called.  We must not get the alloc page in cache because it may be
+        stale page and it may still say the container has not been dropped.
 
 		<BR> MT - single thread required - Enforced by caller.
 
+        @param epage the embryonic page to read the header from
 		@exception StandardException Derby Standard error policy
 		@exception IOException error in reading the header from file
 	*/
-	protected void readHeader(DataInput fileData) 
+	protected void readHeader(byte[] epage)
 		 throws IOException, StandardException
 	{
-		// Always read the header from the input stread even if the alloc page may
-		// still be in cache.  This is because a stubbify operation only writes
-		// the stub to disk, it did not get rid of any stale page from the page
-		// cache.  So if it so happen that the stubbified container object is
-		// aged out of the container cache but the first alloc page hasn't,
-		// then when any stale page of this container wants to be written out,
-		// the container needs to be reopened, which is when this routine is
-		// called.  We must not get the alloc page in cache because it may be
-		// stale page and it may still say the container has not been dropped.
-
-		byte[] epage = getEmbryonicPage(fileData);
-
 		// read persistent container header into containerInfo
 		AllocPage.ReadContainerInfo(containerInfo, epage);
 
 		// initialize header from information stored in containerInfo
 		readHeaderFromArray(containerInfo);
-
-		epage = null;
 	}
 
 	// initialize header information so this container object can be safely
@@ -869,8 +852,7 @@
 	}
 
 	/**
-		Write the container header directly to output stream (fileData).
-		Assumes the output stream is positioned at the beginning of the file.
+		Write the container header directly to file.
 
 		Subclasses that can writes the container header is expected to
 		manufacture a DataOutput stream which is used here.
@@ -880,7 +862,8 @@
 		@exception StandardException Derby Standard error policy
 		@exception IOException error in writing the header to file
 	 */
-	protected void writeHeader(DataOutput fileData, boolean create, byte[] epage)
+	protected void writeHeader(StorageRandomAccessFile file,
+                               boolean create, byte[] epage)
 		 throws IOException, StandardException
 	{
 		// write out the current containerInfo in the borrowed space to byte
@@ -903,7 +886,7 @@
 		dataFactory.writeInProgress();
 		try
 		{
-			fileData.write(epage);
+            writeAtOffset(file, epage, FIRST_ALLOC_PAGE_OFFSET);
 		}
 		finally
 		{
@@ -911,6 +894,23 @@
 		}
 	}
 
+    /**
+     * Write a sequence of bytes at the given offset in a file. This method
+     * is not thread safe, so the caller must make sure that no other thread
+     * is performing operations that may change current position in the file.
+     *
+     * @param file the file to write to
+     * @param bytes the bytes to write
+     * @param offset the offset to start writing at
+     * @throws IOException if an I/O error occurs while writing
+     */
+    void writeAtOffset(StorageRandomAccessFile file, byte[] bytes, long offset)
+            throws IOException
+    {
+        file.seek(offset);
+        file.write(bytes);
+    }
+
 	/**
 		Get an embryonic page from the dataInput stream.
 
@@ -933,6 +933,26 @@
 		}
 		return epage;
 	}
+
+    /**
+     * Read an embryonic page (that is, a section of the first alloc page that
+     * is so large that we know all the borrowed space is included in it) from
+     * the specified offset in a {@code StorageRandomAccessFile}. This method
+     * is not thread safe, so the caller must make sure that no other thread
+     * is performing operations that may change current position in the file.
+     *
+     * @param file the file to read from
+     * @param offset where to start reading (normally
+     * {@code FileContainer.FIRST_ALLOC_PAGE_OFFSET})
+     * @return a byte array containing the embryonic page
+     * @throws IOException if an I/O error occurs while reading
+     */
+    byte[] getEmbryonicPage(StorageRandomAccessFile file, long offset)
+            throws IOException
+    {
+        file.seek(offset);
+        return getEmbryonicPage(file);
+    }
 
 	/**
 		Write containerInfo into a byte array

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/InputStreamContainer.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/InputStreamContainer.java?rev=647139&r1=647138&r2=647139&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/InputStreamContainer.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/InputStreamContainer.java Fri Apr 11 05:30:06 2008
@@ -89,7 +89,7 @@
 			// of the first allocation page. And it is because we
 			// just opened the stream and the first allocation page
 			// is located at the beginning of the file.
-			readHeader(dis);
+			readHeader(getEmbryonicPage(dis));
 
 			return true;
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java?rev=647139&r1=647138&r2=647139&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java Fri Apr 11 05:30:06 2008
@@ -679,14 +679,11 @@
 		}
 		else
 		{
-			file.seek(FIRST_ALLOC_PAGE_OFFSET);
-			epage = getEmbryonicPage(file);
+			epage = getEmbryonicPage(file, FIRST_ALLOC_PAGE_OFFSET);
 		}
 
 		// need to check for frozen state
 
-
-		file.seek(FIRST_ALLOC_PAGE_OFFSET);
 		writeHeader(file, create, epage);
 
 		// leave the end of the file at a page boundry. This
@@ -1393,8 +1390,8 @@
              try {
 
                  fileData = file.getRandomAccessFile(canUpdate ? "rw" : "r");
-                 fileData.seek(FIRST_ALLOC_PAGE_OFFSET);
-                 readHeader(fileData);
+                 readHeader(getEmbryonicPage(fileData,
+                                             FIRST_ALLOC_PAGE_OFFSET));
 
                  if (SanityManager.DEBUG)
                  {
@@ -1435,7 +1432,8 @@
                          fileData = 
                              stub.getRandomAccessFile(canUpdate ? "rw" : "r");
 
-                         readHeader(fileData);
+                         readHeader(getEmbryonicPage(fileData,
+                                                     FIRST_ALLOC_PAGE_OFFSET));
                      }
                      catch (IOException ioe2)
                      {

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer4.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer4.java?rev=647139&r1=647138&r2=647139&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer4.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer4.java Fri Apr 11 05:30:06 2008
@@ -24,9 +24,7 @@
 
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.services.sanity.SanityManager;
-import org.apache.derby.iapi.services.io.FormatIdUtil;
 
-import org.apache.derby.impl.store.raw.data.BaseDataFileFactory;
 import org.apache.derby.iapi.store.raw.ContainerKey;
 
 import java.io.EOFException;
@@ -35,6 +33,7 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.ClosedChannelException;
+import org.apache.derby.io.StorageRandomAccessFile;
 
 /**
  * RAFContainer4 overrides a few methods in RAFContainer in an attempt to use
@@ -84,6 +83,51 @@
         super(factory);
     }
 
+    /**
+     * Return the {@code FileChannel} for the specified
+     * {@code StorageRandomAccessFile} if it is a {@code RandomAccessFile}.
+     * Otherwise, return {@code null}.
+     *
+     * @param file the file to get the channel for
+     * @return a {@code FileChannel} if {@code file} is an instance of
+     * {@code RandomAccessFile}, {@code null} otherwise
+     */
+    private FileChannel getChannel(StorageRandomAccessFile file) {
+        if (file instanceof RandomAccessFile) {
+            /** XXX - this cast isn't testing friendly.
+             * A testing class that implements StorageRandomAccessFile but isn't
+             * a RandomAccessFile will be "worked around" by this class. An
+             * example of such a class is
+             * functionTests/util/corruptio/CorruptRandomAccessFile.java.
+             * An interface rework may be necessary.
+             */
+            return ((RandomAccessFile) file).getChannel();
+        }
+        return null;
+    }
+
+    /**
+     * <p>
+     * Return the file channel for the current value of the {@code fileData}
+     * field. If {@code fileData} doesn't support file channels, return
+     * {@code null}.
+     * </p>
+     *
+     * <p>
+     * Callers of this method must synchronize on the container object since
+     * two shared fields ({@code fileData} and {@code ourChannel}) are
+     * accessed.
+     * </p>
+     *
+     * @return a {@code FileChannel} object, if supported, or {@code null}
+     */
+    private FileChannel getChannel() {
+        if (ourChannel == null) {
+            ourChannel = getChannel(fileData);
+        }
+        return ourChannel;
+    }
+
     /*
      * Wrapping methods that retrieve the FileChannel from RAFContainer's
      * fileData after calling the real methods in RAFContainer.
@@ -95,21 +139,11 @@
             SanityManager.ASSERT(iosInProgress == 0,
                     "Container opened while IO operations are in progress. "
                     + "This should not happen.");
+            SanityManager.ASSERT(fileData == null, "fileData isn't null");
+            SanityManager.ASSERT(ourChannel == null, "ourChannel isn't null");
         }
 
-        boolean result = super.openContainer(newIdentity);
-        if (result == true && super.fileData != null &&
-                super.fileData instanceof java.io.RandomAccessFile) {
-            /** XXX - this cast isn't testing friendly.
-             * A testing class that implements StorageRandomAccessFile but isn't
-             * a RandomAccessFile will be "worked around" by this class. An
-             * example of such a class is
-             * functionTests/util/corruptio/CorruptRandomAccessFile.java.
-             * An interface rework may be necessary.
-             */
-            ourChannel = ((RandomAccessFile)super.fileData).getChannel();
-        }
-        return result;
+        return super.openContainer(newIdentity);
     }
 
     synchronized void createContainer(ContainerKey newIdentity)
@@ -119,14 +153,10 @@
             SanityManager.ASSERT(iosInProgress == 0,
                     "Container created while IO operations are in progress. "
                     + "This should not happen.");
+            SanityManager.ASSERT(fileData == null, "fileData isn't null");
+            SanityManager.ASSERT(ourChannel == null, "ourChannel isn't null");
         }
         super.createContainer(newIdentity);
-
-        if (super.fileData != null &&
-                super.fileData instanceof java.io.RandomAccessFile) {
-            // XXX - see "XXX" comment above.
-            ourChannel = ((RandomAccessFile) super.fileData).getChannel();
-        }
     }
 
 
@@ -188,18 +218,10 @@
     {
         FileChannel ioChannel;
         synchronized (this) {
-            ioChannel = ourChannel;
             if (SanityManager.DEBUG) {
                 SanityManager.ASSERT(!getCommittedDropState());
-                // If ioChannel == null and fileData supports getChannel()
-                // we have a problem. See this.openContainer(ContainerKey 
-                // newIdentity).
-                SanityManager.ASSERT(! ((ioChannel == null) &&
-                        super.fileData instanceof java.io.RandomAccessFile),
-                        "RAFContainer4: New style readPage attempted" +
-                        " with uninitialized ioChannel");
-
             }
+            ioChannel = getChannel();
         }
 
         if(ioChannel != null) {
@@ -272,21 +294,14 @@
          throws IOException, StandardException
     {
         FileChannel ioChannel;
-        synchronized(this) {
+        synchronized (this) {
             // committed and dropped, do nothing.
             // This file container may only be a stub
             if (getCommittedDropState())
                 return;
-            ioChannel = ourChannel;
-            if (SanityManager.DEBUG) {
-                // If ioChannel == null and fileData supports getChannel()
-                // we have a problem
-                SanityManager.ASSERT(! ((ioChannel == null) &&
-                        super.fileData instanceof java.io.RandomAccessFile),
-                        "RAFContainer4: New style writePage attempted " +
-                        "with uninitialized ioChannel");
-            }
+            ioChannel = getChannel();
         }
+
         if(ioChannel != null) {
             ///////////////////////////////////////////////////
             //
@@ -398,6 +413,49 @@
         }
     }
 
+    /**
+     * Write a sequence of bytes at the given offset in a file.
+     *
+     * @param file the file to write to
+     * @param bytes the bytes to write
+     * @param offset the offset to start writing at
+     * @throws IOException if an I/O error occurs while writing
+     */
+    void writeAtOffset(StorageRandomAccessFile file, byte[] bytes, long offset)
+            throws IOException
+    {
+        FileChannel ioChannel = getChannel(file);
+        if (ioChannel != null) {
+            writeFull(ByteBuffer.wrap(bytes), ioChannel, offset);
+        } else {
+            super.writeAtOffset(file, bytes, offset);
+        }
+    }
+
+    /**
+     * Read an embryonic page (that is, a section of the first alloc page that
+     * is so large that we know all the borrowed space is included in it) from
+     * the specified offset in a {@code StorageRandomAccessFile}.
+     *
+     * @param file the file to read from
+     * @param offset where to start reading (normally
+     * {@code FileContainer.FIRST_ALLOC_PAGE_OFFSET})
+     * @return a byte array containing the embryonic page
+     * @throws IOException if an I/O error occurs while reading
+     */
+    byte[] getEmbryonicPage(StorageRandomAccessFile file, long offset)
+            throws IOException
+    {
+        FileChannel ioChannel = getChannel(file);
+        if (ioChannel != null) {
+            ByteBuffer buffer =
+                    ByteBuffer.allocate(AllocPage.MAX_BORROWED_SPACE);
+            readFull(buffer, ioChannel, offset);
+            return buffer.array();
+        } else {
+            return super.getEmbryonicPage(file, offset);
+        }
+    }
 
     /**
      * Attempts to fill buf completely from start until it's full.