--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.access.heap.HeapPostCommit\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.access.heap;\r
+\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+import org.apache.derby.iapi.services.daemon.Serviceable;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.store.access.conglomerate.TransactionManager;\r
+\r
+import org.apache.derby.iapi.store.access.AccessFactory;\r
+import org.apache.derby.iapi.store.access.AccessFactoryGlobals;\r
+\r
+import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.RowUtil;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.iapi.store.raw.ContainerHandle;\r
+import org.apache.derby.iapi.store.raw.LockingPolicy;\r
+import org.apache.derby.iapi.store.raw.Page;\r
+import org.apache.derby.iapi.store.raw.RecordHandle;\r
+import org.apache.derby.iapi.store.raw.Transaction;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+/**\r
+\r
+The HeapPostCommit class implements the Serviceable protocol. \r
+\r
+In it's role as a Serviceable object, it stores the state necessary to \r
+find a page in a heap that may have committed delete's to reclaim.\r
+\r
+It looks up the page described, and reclaims space in the conglomerate. \r
+It first trys to clean up any deleted commits on the page. It will then \r
+deallocate the page if no rows remain on the page. All work is done while\r
+holding the latch on the page, and locks are never "waited" on while holding\r
+this latch.\r
+\r
+This implementation uses record level locking to reclaim the space. \r
+For the protocols to work correctly all other heap methods must be \r
+prepared for a record or a page to "disappear" if they don't hold a latch and/or\r
+a lock. An example of the problem case is a scan which does not hold locks\r
+on it's current position (group scan works this way), which is positioned\r
+on a row deleted by another xact, it must be prepared to continue the \r
+scan after getting an error if the current page/row disapppears.\r
+\r
+**/\r
+\r
+class HeapPostCommit implements Serviceable\r
+{\r
+ /**************************************************************************\r
+ * Fields of the class\r
+ **************************************************************************\r
+ */\r
+\r
+ private AccessFactory access_factory = null;\r
+ private Heap heap = null;\r
+ private long page_number = ContainerHandle.INVALID_PAGE_NUMBER;\r
+\r
+\r
+ /**************************************************************************\r
+ * Constructors for This class:\r
+ **************************************************************************\r
+ */\r
+ HeapPostCommit(\r
+ AccessFactory access_factory,\r
+ Heap heap,\r
+ long input_page_number)\r
+ {\r
+ this.access_factory = access_factory; \r
+ this.heap = heap; \r
+ this.page_number = input_page_number; \r
+ }\r
+\r
+ /**************************************************************************\r
+ * Private/Protected methods of This class:\r
+ **************************************************************************\r
+ */\r
+\r
+ /**\r
+ * Reclaim space taken up by committed deleted rows.\r
+ * <p>\r
+ * This routine assumes it has been called by an internal transaction which\r
+ * has performed no work so far, and that it has an exclusive intent table \r
+ * lock. It will attempt obtain exclusive row locks on deleted rows, where\r
+ * successful those rows can be reclaimed as they must be "committed \r
+ * deleted" rows.\r
+ * <p>\r
+ * This routine will latch the page and hold the latch due to interface\r
+ * requirement from Page.purgeAtSlot.\r
+ *\r
+ * @param heap_control The heap, already opened.\r
+ * @param pageno number of page to look for committed deletes.\r
+ *\r
+ * @see Page#purgeAtSlot\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ private final void purgeCommittedDeletes(\r
+ HeapController heap_control,\r
+ long pageno)\r
+ throws StandardException\r
+ {\r
+ // The following can fail either if it can't get the latch or\r
+ // somehow the page requested no longer exists. \r
+ \r
+ //resolve - what will happen if the user page doesnt exist \r
+\r
+ // wait to get the latch on the page \r
+ Page page = heap_control.getUserPageWait(pageno);\r
+ boolean purgingDone = false;\r
+\r
+ if (page != null)\r
+ {\r
+ try\r
+ {\r
+ // The number records that can be reclaimed is:\r
+ // total recs - recs_not_deleted\r
+ int num_possible_commit_delete = \r
+ page.recordCount() - page.nonDeletedRecordCount();\r
+\r
+ if (num_possible_commit_delete > 0)\r
+ {\r
+ // loop backward so that purges which affect the slot table \r
+ // don't affect the loop (ie. they only move records we \r
+ // have already looked at).\r
+ for (int slot_no = page.recordCount() - 1; \r
+ slot_no >= 0; \r
+ slot_no--) \r
+ {\r
+ boolean row_is_committed_delete = \r
+ page.isDeletedAtSlot(slot_no);\r
+\r
+ if (row_is_committed_delete)\r
+ {\r
+ // At this point we only know that the row is\r
+ // deleted, not whether it is committed.\r
+\r
+ // see if we can purge the row, by getting an\r
+ // exclusive lock on the row. If it is marked\r
+ // deleted and we can get this lock, then it\r
+ // must be a committed delete and we can purge \r
+ // it.\r
+\r
+ RecordHandle rh =\r
+ page.fetchFromSlot(\r
+ (RecordHandle) null,\r
+ slot_no,\r
+ RowUtil.EMPTY_ROW,\r
+ RowUtil.EMPTY_ROW_FETCH_DESCRIPTOR,\r
+ true);\r
+\r
+ row_is_committed_delete =\r
+ heap_control.lockRowAtSlotNoWaitExclusive(rh);\r
+\r
+ if (row_is_committed_delete)\r
+ {\r
+ purgingDone = true;\r
+\r
+ page.purgeAtSlot(slot_no, 1, false);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(\r
+ "verbose_heap_post_commit"))\r
+ {\r
+ SanityManager.DEBUG_PRINT(\r
+ "HeapPostCommit", \r
+ "Purging row[" + slot_no + "]" + \r
+ "on page:" + pageno + ".\n");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ if (page.recordCount() == 0)\r
+ {\r
+ purgingDone = true;\r
+\r
+ // Deallocate the current page with 0 rows on it.\r
+ heap_control.removePage(page);\r
+\r
+ // removePage guarantees to unlatch the page even if an\r
+ // exception is thrown. The page is protected against reuse\r
+ // because removePage locks it with a dealloc lock, so it\r
+ // is OK to release the latch even after a purgeAtSlot is\r
+ // called.\r
+ // @see ContainerHandle#removePage\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("verbose_heap_post_commit"))\r
+ {\r
+ SanityManager.DEBUG_PRINT(\r
+ "HeapPostCommit", \r
+ "Calling Heap removePage().; pagenumber="+pageno+"\n");\r
+ }\r
+ }\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ // If no purge happened on the page and the page is not\r
+ // removed, feel free to unlatch it. Otherwise, let\r
+ // transaction commit take care of it.\r
+ if (!purgingDone)\r
+ {\r
+ page.unlatch();\r
+ page = null;\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("verbose_heap_post_commit"))\r
+ {\r
+ SanityManager.DEBUG_PRINT(\r
+ "HeapPostCommit", \r
+ "Get No Wait returned null. page num = " + \r
+ pageno + "\n");\r
+\r
+ SanityManager.showTrace(new Throwable());\r
+ }\r
+ }\r
+ }\r
+ return;\r
+ }\r
+\r
+ /**************************************************************************\r
+ * Public Methods implementing the Serviceable interface:\r
+ **************************************************************************\r
+ */\r
+\r
+ /**\r
+ * The urgency of this post commit work.\r
+ * <p>\r
+ * This determines where this Serviceable is put in the post commit \r
+ * queue. Post commit work in the heap can be safely delayed until there\r
+ * is not user work to do.\r
+ *\r
+ * @return false, this work should not be serviced ASAP\r
+ **/\r
+ public boolean serviceASAP()\r
+ {\r
+ return(true);\r
+ }\r
+\r
+ // @return true, if this work needs to be done on a user thread immediately\r
+ public boolean serviceImmediately()\r
+ {\r
+ return false;\r
+ } \r
+\r
+\r
+ /**\r
+ * perform the work described in the postcommit work.\r
+ * <p>\r
+ * In this implementation the only work that can be executed by this\r
+ * post commit processor is this class itself.\r
+ * <p>\r
+ *\r
+ * @return Returns Serviceable.DONE when work has completed, or\r
+ * returns Serviceable.REQUEUE if work needs to be requeued.\r
+ *\r
+ * @param contextMgr the context manager started by the post commit daemon\r
+ *\r
+ * @exception StandardException Standard exception policy.\r
+ **/\r
+ public int performWork(ContextManager contextMgr)\r
+ throws StandardException\r
+ {\r
+ TransactionManager tc = (TransactionManager)\r
+ this.access_factory.getAndNameTransaction(\r
+ contextMgr, AccessFactoryGlobals.SYS_TRANS_NAME);\r
+\r
+ TransactionManager internal_xact = tc.getInternalTransaction();\r
+\r
+ // only requeue if work was not completed in this try.\r
+ boolean requeue_work = false;\r
+\r
+ HeapController heapcontroller;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("verbose_heap_post_commit"))\r
+ SanityManager.DEBUG_PRINT(\r
+ "HeapPostCommit", "starting internal xact\n");\r
+ }\r
+\r
+ try\r
+ {\r
+ // This call will attempt to open the heap table locked with \r
+ // table level IX mode, preparing to do record level locked space \r
+ // reclamation. \r
+ //\r
+ // The call will either succeed immediately, or throw an exception\r
+ // which could mean the container does not exist or that the lock\r
+ // could not be granted immediately. \r
+\r
+ //Reversed the fix for 4255:\r
+ //page reclaimation is done asynchronosly by raswstore daemon\r
+ //not good to WAIT FOR LOCKS , as it can freeze the daemon\r
+ //If we can not get the lock this reclamation request will \r
+ //requeued.\r
+\r
+ heapcontroller = (HeapController)\r
+ heap.open(\r
+ internal_xact,\r
+ internal_xact.getRawStoreXact(),\r
+ false,\r
+ ContainerHandle.MODE_FORUPDATE |\r
+ ContainerHandle.MODE_LOCK_NOWAIT,\r
+ TransactionController.MODE_RECORD,\r
+ internal_xact.getRawStoreXact().newLockingPolicy(\r
+ LockingPolicy.MODE_RECORD,\r
+ TransactionController.ISOLATION_REPEATABLE_READ, true),\r
+ heap,\r
+ (DynamicCompiledOpenConglomInfo) null);\r
+\r
+ // We got a table intent lock, all deleted rows we encounter can\r
+ // be reclaimed, once an "X" row lock is obtained on them.\r
+\r
+ // Process all the rows on the page while holding the latch.\r
+ purgeCommittedDeletes(heapcontroller, this.page_number);\r
+\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ // exception might have occured either container got dropper or lock not granted.\r
+ // It is possible by the time this post commit work gets scheduled \r
+ // that the container has been dropped and that the open container \r
+ // call will return null - in this case just return assuming no \r
+ // work to be done.\r
+\r
+ //If this expcetion is because lock could not be obtained , work is requeued.\r
+ if (se.getMessageId().equals(SQLState.LOCK_TIMEOUT) || \r
+ se.getMessageId().equals(SQLState.DEADLOCK))\r
+ {\r
+ requeue_work = true;\r
+ }\r
+\r
+ // Do not close the controller because that will unlatch the\r
+ // page. Let the commit and destroy do release the latch and\r
+ // close the controller.\r
+ // heapcontroller.close();\r
+ }\r
+ \r
+ // It is ok to not sync this post work. If no subsequent log record\r
+ // is sync'd to disk then it is ok that this transaction not make\r
+ // it to the database. If any subsequent transaction is sync'd to\r
+ // the log file, then this transaction will be sync'd as part of that\r
+ // work.\r
+\r
+ internal_xact.commitNoSync(Transaction.RELEASE_LOCKS);\r
+ internal_xact.destroy();\r
+\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON("verbose_heap_post_commit"))\r
+ {\r
+ if (requeue_work)\r
+ SanityManager.DEBUG_PRINT(\r
+ "HeapPostCommit", \r
+ "requeueing on page num = " + page_number);\r
+ }\r
+ }\r
+\r
+ return(requeue_work ? Serviceable.REQUEUE : Serviceable.DONE);\r
+ }\r
+}\r
+\r