--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.data.CachedPage\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.data;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.store.raw.log.LogInstant;\r
+import org.apache.derby.iapi.store.raw.PageKey;\r
+\r
+import org.apache.derby.iapi.services.cache.Cacheable;\r
+import org.apache.derby.iapi.services.cache.CacheManager;\r
+\r
+import org.apache.derby.iapi.services.monitor.Monitor;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.io.FormatIdUtil;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.error.ExceptionSeverity;\r
+import java.io.IOException;\r
+\r
+/**\r
+ A base page that is cached.\r
+\r
+ Since there are multiple page formats, use this abstract class to implement\r
+ cacheable interface.\r
+\r
+*/\r
+\r
+public abstract class CachedPage extends BasePage implements Cacheable\r
+{\r
+ protected boolean alreadyReadPage; // true when page read by another \r
+ // class\r
+\r
+ protected byte[] pageData; // the actual page data - this is\r
+ // the 'buffer' in the buffer cache\r
+\r
+ // The isDirty flag indicates if the pageData or pageHeader has been\r
+ // modified. The preDirty flag indicates that the pageData or the\r
+ // pageHeader is about to be modified. The reason for these 2 flags\r
+ // instead of just one is to accomodate checkpoint. After a clean\r
+ // (latched) page sends a log record to the log stream but before that page\r
+ // is dirtied by the log operation, a checkpoint could be taken. If so,\r
+ // then the redoLWM will be after the log record but, without preDirty, the\r
+ // cache cleaning will not have waited for the change. So the preDirty bit\r
+ // is to stop the cache cleaning from skipping over this (latched) page\r
+ // even though it has not really been modified yet. \r
+\r
+ protected boolean isDirty; // must be set to true whenever the\r
+ // pageData array is touched \r
+ // directly or indirectly.\r
+\r
+ protected boolean preDirty; // set to true if the page is clean\r
+ // and the pageData array is about \r
+ // to be touched directly or \r
+ // indirectly.\r
+\r
+\r
+ protected int initialRowCount; // keep a running count of rows for\r
+ // estimated row count.\r
+\r
+ private long containerRowCount; // the number of rows in the\r
+ // container when this page is read\r
+ // from disk \r
+\r
+ /*\r
+ ** These fields are immutable and can be used by the subclasses directly.\r
+ */\r
+\r
+ /**\r
+ The page cache I live in.\r
+\r
+ <BR> MT - Immutable\r
+ */\r
+ protected CacheManager pageCache;\r
+\r
+ /**\r
+ The container cache my container lives in.\r
+\r
+ <BR> MT - Immutable\r
+ */\r
+ protected CacheManager containerCache;\r
+\r
+ /**\r
+ My factory class.\r
+\r
+ <BR> MT - Immutable - \r
+ */\r
+ protected BaseDataFileFactory dataFactory; // my factory class.\r
+\r
+\r
+ protected static final int PAGE_FORMAT_ID_SIZE = 4;\r
+\r
+ /*\r
+ * the page need to be written and synced to disk \r
+ */\r
+ public static final int WRITE_SYNC = 1;\r
+\r
+ /*\r
+ * the page need to be write to disk but not synced\r
+ */\r
+ public static final int WRITE_NO_SYNC = 2;\r
+\r
+ public CachedPage()\r
+ {\r
+ super();\r
+ }\r
+\r
+ public final void setFactory(BaseDataFileFactory factory) \r
+ {\r
+ dataFactory = factory;\r
+ pageCache = factory.getPageCache();\r
+ containerCache = factory.getContainerCache();\r
+ }\r
+\r
+ /**\r
+ Initialize a CachedPage.\r
+ <p>\r
+ Initialize the object, ie. perform work normally perfomed in \r
+ constructor. Called by setIdentity() and createIdentity().\r
+ */\r
+ protected void initialize()\r
+ {\r
+ super.initialize();\r
+ isDirty = false;\r
+ preDirty = false;\r
+ initialRowCount = 0;\r
+ containerRowCount = 0;\r
+ }\r
+\r
+ /*\r
+ ** Methods of Cacheable\r
+ */\r
+\r
+ /**\r
+ * Find the container and then read the page from that container.\r
+ * <p>\r
+ * This is the way new pages enter the page cache.\r
+ * <p>\r
+ *\r
+ * @return always true, higher levels have already checked the page number \r
+ * is valid for an open.\r
+ *\r
+ * @exception StandardException Standard Derby policy.\r
+ *\r
+ * @see Cacheable#setIdentity\r
+ **/\r
+ public Cacheable setIdentity(Object key) \r
+ throws StandardException \r
+ {\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ SanityManager.ASSERT(key instanceof PageKey);\r
+ }\r
+\r
+ initialize();\r
+\r
+ PageKey newIdentity = (PageKey) key;\r
+\r
+ FileContainer myContainer = \r
+ (FileContainer) containerCache.find(newIdentity.getContainerId());\r
+\r
+ setContainerRowCount(myContainer.getEstimatedRowCount(0));\r
+\r
+ try\r
+ {\r
+ if (!alreadyReadPage)\r
+ {\r
+ // Fill in the pageData array by reading bytes from disk.\r
+ readPage(myContainer, newIdentity); \r
+ }\r
+ else\r
+ {\r
+ // pageData array already filled\r
+ alreadyReadPage = false;\r
+ }\r
+\r
+ // if the formatID on disk is not the same as this page instance's\r
+ // format id, instantiate the real page object\r
+ int fmtId = getTypeFormatId();\r
+\r
+ int onPageFormatId = FormatIdUtil.readFormatIdInteger(pageData);\r
+ if (fmtId != onPageFormatId)\r
+ {\r
+ return changeInstanceTo(\r
+ onPageFormatId, newIdentity).setIdentity(key);\r
+ }\r
+\r
+ // this is the correct page instance\r
+ initFromData(myContainer, newIdentity);\r
+ }\r
+ finally\r
+ {\r
+ containerCache.release(myContainer);\r
+ myContainer = null;\r
+ }\r
+\r
+ fillInIdentity(newIdentity);\r
+\r
+ initialRowCount = 0;\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Find the container and then create the page in that container.\r
+ * <p>\r
+ * This is the process of creating a new page in a container, in that\r
+ * case no need to read the page from disk - just need to initialize it\r
+ * in the cache.\r
+ * <p>\r
+ *\r
+ * @return new page, higher levels have already checked the page number is \r
+ * valid for an open.\r
+ *\r
+ * @param key Which page is this?\r
+ * @param createParameter details needed to create page like size, \r
+ * format id, ...\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ *\r
+ * @see Cacheable#createIdentity\r
+ **/\r
+ public Cacheable createIdentity(\r
+ Object key, \r
+ Object createParameter) \r
+ throws StandardException \r
+ {\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ SanityManager.ASSERT(key instanceof PageKey);\r
+ }\r
+\r
+ initialize();\r
+\r
+ PageKey newIdentity = (PageKey) key;\r
+\r
+ PageCreationArgs createArgs = (PageCreationArgs) createParameter;\r
+ int formatId = createArgs.formatId;\r
+\r
+ if (formatId == -1)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNKNOWN_PAGE_FORMAT, newIdentity);\r
+ }\r
+\r
+ // createArgs[0] contains the integer form of the formatId \r
+ // if it is not the same as this instance's formatId, instantiate the\r
+ // real page object\r
+ if (formatId != getTypeFormatId())\r
+ {\r
+ return(\r
+ changeInstanceTo(formatId, newIdentity).createIdentity(\r
+ key, createParameter));\r
+ }\r
+ \r
+ // this is the correct page instance\r
+ initializeHeaders(5);\r
+ createPage(newIdentity, createArgs);\r
+\r
+ fillInIdentity(newIdentity);\r
+\r
+ initialRowCount = 0;\r
+\r
+ /*\r
+ * if we need to grow the container and the page has not been\r
+ * preallocated, writing page before the log is written so that we\r
+ * know if there is an IO error - like running out of disk space - then\r
+ * we don't write out the log record, because if we do, it may fail\r
+ * after the log goes to disk and then the database may not be\r
+ * recoverable. \r
+ *\r
+ * WRITE_SYNC is used when we create the page without first\r
+ * preallocating it \r
+ * WRITE_NO_SYNC is used when we are preallocating the page - there\r
+ * will be a SYNC call after all the pages are preallocated\r
+ * 0 means creating a page that has already been preallocated.\r
+ */\r
+ int syncFlag = createArgs.syncFlag;\r
+ if ((syncFlag & WRITE_SYNC) != 0 ||\r
+ (syncFlag & WRITE_NO_SYNC) != 0)\r
+ writePage(newIdentity, (syncFlag & WRITE_SYNC) != 0);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(FileContainer.SPACE_TRACE))\r
+ {\r
+ String sync =\r
+ ((syncFlag & WRITE_SYNC) != 0) ? "Write_Sync" :\r
+ (((syncFlag & WRITE_NO_SYNC) != 0) ? "Write_NO_Sync" :\r
+ "No_write");\r
+\r
+ SanityManager.DEBUG(\r
+ FileContainer.SPACE_TRACE,\r
+ "creating new page " + newIdentity + " with " + sync);\r
+ }\r
+ }\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Convert this page to requested type, as defined by input format id.\r
+ * <p>\r
+ * The current cache entry is a different format id than the requested\r
+ * type, change it. This object is instantiated to the wrong subtype of \r
+ * cachedPage, this routine will create an object with the correct subtype,\r
+ * and transfer all pertinent information from this to the new correct \r
+ * object.\r
+ * <p>\r
+ *\r
+ * @return The new object created with the input fid and transfered info.\r
+ *\r
+ * @param fid The format id of the new page.\r
+ * @param newIdentity The key of the new page.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private CachedPage changeInstanceTo(int fid, PageKey newIdentity)\r
+ throws StandardException\r
+ {\r
+ CachedPage realPage;\r
+ try \r
+ {\r
+ realPage = \r
+ (CachedPage) Monitor.newInstanceFromIdentifier(fid);\r
+\r
+ } \r
+ catch (StandardException se) \r
+ {\r
+ if (se.getSeverity() > ExceptionSeverity.STATEMENT_SEVERITY)\r
+ {\r
+ throw se;\r
+ }\r
+ else\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.DATA_UNKNOWN_PAGE_FORMAT, se, newIdentity);\r
+ }\r
+ }\r
+\r
+ realPage.setFactory(dataFactory);\r
+\r
+ // avoid creating the data buffer if possible, transfer it to the new \r
+ // page if this is the first time the page buffer is used, then \r
+ // createPage will create the page array with the correct page size\r
+ if (this.pageData != null) \r
+ {\r
+ realPage.alreadyReadPage = true;\r
+ realPage.usePageBuffer(this.pageData);\r
+ }\r
+\r
+ // RESOLVE (12/15/06) - the following code is commented out, but\r
+ // not sure why.\r
+\r
+ // this page should not be used any more, null out all its content and\r
+ // wait for GC to clean it up \r
+\r
+ //destroyPage();// let this subtype have a chance to get rid of stuff\r
+ //this.pageData = null; // this instance no longer own the data array\r
+ //this.pageCache = null;\r
+ //this.dataFactory = null;\r
+ //this.containerCache = null;\r
+\r
+ return realPage;\r
+ }\r
+\r
+ /**\r
+ * Is the page dirty?\r
+ * <p>\r
+ * The isDirty flag indicates if the pageData or pageHeader has been\r
+ * modified. The preDirty flag indicates that the pageData or the\r
+ * pageHeader is about to be modified. The reason for these 2 flags\r
+ * instead of just one is to accomodate checkpoint. After a clean\r
+ * (latched) page sends a log record to the log stream but before that page\r
+ * is dirtied by the log operation, a checkpoint could be taken. If so,\r
+ * then the redoLWM will be after the log record but, without preDirty, the\r
+ * cache cleaning will not have waited for the change. So the preDirty bit\r
+ * is to stop the cache cleaning from skipping over this (latched) page\r
+ * even though it has not really been modified yet. \r
+ *\r
+ * @return true if the page is dirty.\r
+ *\r
+ * @see Cacheable#isDirty\r
+ **/\r
+ public boolean isDirty() \r
+ {\r
+ synchronized (this) \r
+ {\r
+ return isDirty || preDirty;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Has the page or its header been modified.\r
+ * <p>\r
+ * See comment on class header on meaning of isDirty and preDirty bits.\r
+ * <p>\r
+ *\r
+ * @return true if changes have actually been made to the page in memory.\r
+ **/\r
+ public boolean isActuallyDirty() \r
+ {\r
+ synchronized (this) \r
+ {\r
+ return isDirty;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set state to indicate the page or its header is about to be modified.\r
+ * <p>\r
+ * See comment on class header on meaning of isDirty and preDirty bits.\r
+ **/\r
+ public void preDirty()\r
+ {\r
+ synchronized (this) \r
+ {\r
+ if (!isDirty)\r
+ preDirty = true;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set state to indicate the page or its header has been modified.\r
+ * <p>\r
+ * See comment on class header on meaning of isDirty and preDirty bits.\r
+ * <p>\r
+ **/\r
+ protected void setDirty() \r
+ {\r
+ synchronized (this) \r
+ {\r
+ isDirty = true;\r
+ preDirty = false;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * exclusive latch on page is being released.\r
+ * <p>\r
+ * The only work done in CachedPage is to update the row count on the\r
+ * container if it is too out of sync.\r
+ **/\r
+ protected void releaseExclusive()\r
+ {\r
+ // look at dirty bit without latching, the updating of the row\r
+ // count is just an optimization so does not need the latch.\r
+ //\r
+ // if this page actually has > 1/8 rows of the entire container, then\r
+ // consider updating the row count if it is different.\r
+ //\r
+ // No need to special case allocation pages because it has recordCount \r
+ // of zero, thus the if clause will never be true for an allocation \r
+ // page.\r
+ if (isDirty && !isOverflowPage() &&\r
+ (containerRowCount / 8) < recordCount())\r
+ {\r
+ int currentRowCount = internalNonDeletedRecordCount(); \r
+ int delta = currentRowCount-initialRowCount;\r
+ int posDelta = delta > 0 ? delta : (-delta);\r
+\r
+ if ((containerRowCount/8) < posDelta)\r
+ {\r
+ // This pages delta row count represents a significant change\r
+ // with respect to current container row count so update \r
+ // container row count\r
+ FileContainer myContainer = null;\r
+\r
+ try\r
+ {\r
+ myContainer = (FileContainer) \r
+ containerCache.find(identity.getContainerId());\r
+\r
+ if (myContainer != null)\r
+ {\r
+ myContainer.updateEstimatedRowCount(delta);\r
+ setContainerRowCount(\r
+ myContainer.getEstimatedRowCount(0));\r
+\r
+ initialRowCount = currentRowCount;\r
+\r
+ // since I have the container, might as well update the\r
+ // unfilled information\r
+ myContainer.trackUnfilledPage(\r
+ identity.getPageNumber(), unfilled());\r
+ }\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ // do nothing, not sure what could fail but this update\r
+ // is just an optimization so no need to throw error.\r
+ }\r
+ finally\r
+ {\r
+ if (myContainer != null)\r
+ containerCache.release(myContainer);\r
+ }\r
+ }\r
+ }\r
+\r
+ super.releaseExclusive();\r
+ }\r
+\r
+\r
+ /**\r
+ * Write the page to disk.\r
+ * <p>\r
+ * MP - In a simple world we would just not allow clean until it held the\r
+ * latch on the page. But in order to fit into the cache system, we \r
+ * don't have enough state around to just make clean() latch the page \r
+ * while doing the I/O - but we still need someway to insure that no\r
+ * changes happen to the page while the I/O is taking place. \r
+ * Also someday it would be fine to allow reads of this page\r
+ * while the I/O was taking place. \r
+ *\r
+ *\r
+ * @exception StandardException Error writing the page.\r
+ *\r
+ * @see Cacheable#clean\r
+ **/\r
+ public void clean(boolean remove) throws StandardException \r
+ {\r
+\r
+ // must wait for the page to be unlatched\r
+ synchronized (this) \r
+ {\r
+ if (!isDirty())\r
+ return;\r
+\r
+ // is someone else cleaning it\r
+ while (inClean) \r
+ {\r
+ try \r
+ {\r
+ wait();\r
+ } \r
+ catch (InterruptedException ie) \r
+ {\r
+ throw StandardException.interrupt(ie);\r
+ }\r
+ }\r
+\r
+ // page is not "inClean" by other thread at this point.\r
+\r
+ if (!isDirty())\r
+ return;\r
+\r
+ inClean = true;\r
+\r
+ // If page is in LATCHED state (as opposed to UNLATCH or PRELATCH)\r
+ // wait for the page to move to UNLATCHED state. See Comments in\r
+ // Generic/BasePage.java describing the interaction of inClean,\r
+ // (owner != null), and preLatch.\r
+ while ((owner != null) && !preLatch) \r
+ {\r
+ try \r
+ { \r
+ wait();\r
+ } \r
+ catch (InterruptedException ie) \r
+ {\r
+ inClean = false;\r
+ throw StandardException.interrupt(ie);\r
+ }\r
+ }\r
+\r
+ // The page is now effectively latched by the cleaner.\r
+ // We only want to clean the page if the page is actually dirtied,\r
+ // not when it is just pre-dirtied.\r
+ if (!isActuallyDirty()) \r
+ {\r
+ // the person who latched it gives up the\r
+ // latch without really dirtying the page\r
+ preDirty = false; \r
+ inClean = false;\r
+ notifyAll();\r
+ return;\r
+ }\r
+ }\r
+\r
+ try\r
+ {\r
+ writePage(getPageId(), false);\r
+ }\r
+ catch(StandardException se)\r
+ {\r
+ // If we get an error while trying to write a page, current\r
+ // recovery system requires that entire DB is shutdown. Then\r
+ // when system is rebooted we will run redo recovery which \r
+ // if it does not encounter disk errors will guarantee to recover\r
+ // to a transaction consistent state. If this write is a \r
+ // persistent device problem, redo recovery will likely fail\r
+ // attempting to the same I/O. Mark corrupt will stop all further\r
+ // writes of data and log by the system.\r
+ throw dataFactory.markCorrupt(se);\r
+ }\r
+ finally\r
+ {\r
+ // if there is something wrong in writing out the page, \r
+ // do not leave it inClean state or it will block the next cleaner \r
+ // forever\r
+\r
+ synchronized (this) \r
+ {\r
+ inClean = false;\r
+ notifyAll();\r
+ }\r
+ }\r
+ }\r
+\r
+ public void clearIdentity() \r
+ {\r
+ alreadyReadPage = false;\r
+ super.clearIdentity();\r
+ }\r
+\r
+ /**\r
+ * read the page from disk into this CachedPage object.\r
+ * <p>\r
+ * A page is read in from disk into the pageData array of this object,\r
+ * and then put in the cache.\r
+ * <p>\r
+ *\r
+ * @param myContainer the container to read the page from.\r
+ * @param newIdentity indentity (ie. page number) of the page to read\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private void readPage(\r
+ FileContainer myContainer, \r
+ PageKey newIdentity) \r
+ throws StandardException \r
+ {\r
+ int pagesize = myContainer.getPageSize();\r
+\r
+ // we will reuse the existing page array if it is same size, the\r
+ // cache does support caching various sized pages.\r
+ setPageArray(pagesize);\r
+\r
+ for (int io_retry_count = 0;;)\r
+ {\r
+ try \r
+ {\r
+ myContainer.readPage(newIdentity.getPageNumber(), pageData);\r
+ break;\r
+ } \r
+ catch (IOException ioe) \r
+ {\r
+ io_retry_count++; \r
+ \r
+ // Retrying read I/O's has been found to be successful sometimes\r
+ // in completing the read without having to fail the calling\r
+ // query, and in some cases avoiding complete db shutdown.\r
+ // Some situations are:\r
+ // spurious interrupts being sent to thread by clients.\r
+ // unreliable hardware like a network mounted file system.\r
+ //\r
+ // The only option other than retrying is to fail the I/O \r
+ // immediately and throwing an error, thus performance cost\r
+ // not really a consideration.\r
+ //\r
+ // The retry max of 4 is arbitrary, but has been enough that\r
+ // not many read I/O errors have been reported.\r
+ if (io_retry_count > 4)\r
+ {\r
+ // page cannot be physically read\r
+ \r
+ StandardException se = \r
+ StandardException.newException(\r
+ SQLState.FILE_READ_PAGE_EXCEPTION, \r
+ ioe, newIdentity, new Integer(pagesize));\r
+\r
+ \r
+ if (dataFactory.getLogFactory().inRFR())\r
+ {\r
+ //if in rollforward recovery, it is possible that this \r
+ //page actually does not exist on the disk yet because\r
+ //the log record we are proccessing now is actually \r
+ //creating the page, we will recreate the page if we \r
+ //are in rollforward recovery, so just throw the \r
+ //exception.\r
+ throw se;\r
+ }\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // by shutting down system in debug mode, maybe\r
+ // we can catch root cause of the interrupt.\r
+ throw dataFactory.markCorrupt(se);\r
+ }\r
+ else\r
+ {\r
+ // No need to shut down runtime database on read\r
+ // error in delivered system, throwing exception \r
+ // should be enough. Thrown exception has nested\r
+ // IO exception which is root cause of error.\r
+ throw se;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * write the page from this CachedPage object to disk.\r
+ * <p>\r
+ *\r
+ * @param identity indentity (ie. page number) of the page to read\r
+ * @param syncMe does the write of this single page have to be sync'd?\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private void writePage(\r
+ PageKey identity, \r
+ boolean syncMe) \r
+ throws StandardException \r
+ {\r
+\r
+ // make subclass write the page format\r
+ writeFormatId(identity); \r
+\r
+ // let subclass have a chance to write any cached data to page data \r
+ // array\r
+ writePage(identity); \r
+\r
+ // force WAL - and check to see if database is corrupt or is frozen.\r
+ // last log Instant may be null if the page is being forced\r
+ // to disk on a createPage (which violates the WAL protocol actually).\r
+ // See FileContainer.newPage\r
+ LogInstant flushLogTo = getLastLogInstant();\r
+ dataFactory.flush(flushLogTo);\r
+\r
+ if (flushLogTo != null) \r
+ { \r
+ clearLastLogInstant();\r
+ }\r
+\r
+\r
+ // find the container and file access object\r
+ FileContainer myContainer = \r
+ (FileContainer) containerCache.find(identity.getContainerId());\r
+\r
+ if (myContainer == null)\r
+ {\r
+ StandardException nested =\r
+ StandardException.newException(\r
+ SQLState.DATA_CONTAINER_VANISHED,\r
+ identity.getContainerId());\r
+ throw dataFactory.markCorrupt(\r
+ StandardException.newException(\r
+ SQLState.FILE_WRITE_PAGE_EXCEPTION, nested,\r
+ identity));\r
+ }\r
+\r
+ try\r
+ {\r
+ myContainer.writePage(\r
+ identity.getPageNumber(), pageData, syncMe);\r
+\r
+ //\r
+ // Do some in memory unlogged bookkeeping tasks while we have\r
+ // the container.\r
+ //\r
+\r
+ if (!isOverflowPage() && isDirty())\r
+ {\r
+\r
+ // let the container knows whether this page is a not\r
+ // filled, non-overflow page\r
+ myContainer.trackUnfilledPage(\r
+ identity.getPageNumber(), unfilled());\r
+\r
+ // if this is not an overflow page, see if the page's row\r
+ // count has changed since it come into the cache.\r
+ //\r
+ // if the page is not invalid, row count is 0. Otherwise,\r
+ // count non-deleted records on page.\r
+ //\r
+ // Cannot call nonDeletedRecordCount because the page is\r
+ // unlatched now even though nobody is changing it\r
+ int currentRowCount = internalNonDeletedRecordCount();\r
+\r
+ if (currentRowCount != initialRowCount)\r
+ {\r
+ myContainer.updateEstimatedRowCount(\r
+ currentRowCount - initialRowCount);\r
+\r
+ setContainerRowCount(\r
+ myContainer.getEstimatedRowCount(0));\r
+\r
+ initialRowCount = currentRowCount;\r
+ }\r
+ }\r
+\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ // page cannot be written\r
+ throw StandardException.newException(\r
+ SQLState.FILE_WRITE_PAGE_EXCEPTION,\r
+ ioe, identity);\r
+ }\r
+ finally\r
+ {\r
+ containerCache.release(myContainer);\r
+ myContainer = null;\r
+ }\r
+\r
+ synchronized (this) \r
+ {\r
+ // change page state to not dirty after the successful write\r
+ isDirty = false;\r
+ preDirty = false;\r
+ }\r
+ }\r
+\r
+ public void setContainerRowCount(long rowCount)\r
+ {\r
+ containerRowCount = rowCount;\r
+ }\r
+\r
+ /**\r
+ ** if the page size is different from the page buffer, then make a\r
+ ** new page buffer and make subclass use the new page buffer\r
+ */\r
+ protected void setPageArray(int pageSize)\r
+ {\r
+ if ((pageData == null) || (pageData.length != pageSize)) \r
+ {\r
+ // Give a chance for garbage collection to free\r
+ // the old array before the new array is allocated.\r
+ // Just in case memory is low.\r
+ pageData = null; \r
+ pageData = new byte[pageSize];\r
+ \r
+ usePageBuffer(pageData);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Returns the page data array used to write on disk version.\r
+ *\r
+ * <p>\r
+ * returns the page data array, that is actually written to the disk,\r
+ * when the page is cleaned from the page cache. Takes care of flushing\r
+ * in-memory information to the array (like page header and format id info).\r
+ * <p>\r
+ *\r
+ * @return The array of bytes that is the on disk version of page.\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ protected byte[] getPageArray() throws StandardException \r
+ {\r
+ // make subclass write the page format\r
+ writeFormatId(identity); \r
+\r
+ // let subclass have a chance to write any cached\r
+ // data to page data array\r
+ writePage(identity); \r
+\r
+ return pageData;\r
+ }\r
+\r
+ /* methods for subclass of cached page */\r
+\r
+ // use a new pageData buffer, initialize in memory structure that depend on\r
+ // the pageData's size. The actual disk data may not have not been read in\r
+ // yet so don't look at the content of the buffer\r
+ protected abstract void usePageBuffer(byte[] buffer);\r
+\r
+\r
+ // initialize in memory structure using the read in buffer in pageData\r
+ protected abstract void initFromData(FileContainer container, PageKey id) \r
+ throws StandardException;\r
+\r
+\r
+ // create the page\r
+ protected abstract void createPage(PageKey id, PageCreationArgs args)\r
+ throws StandardException;\r
+\r
+ // page is about to be written, write everything to pageData array\r
+ protected abstract void writePage(PageKey id) throws StandardException; \r
+\r
+ // write out the formatId to the pageData\r
+ protected abstract void writeFormatId(PageKey identity) \r
+ throws StandardException;\r
+}\r