db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d..@apache.org
Subject svn commit: r666901 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/dictionary/ engine/org/apache/derby/impl/sql/catalog/ storeless/org/apache/derby/impl/storeless/ testing/org/apache/derbyTesting/functionTests/tests/lang/
Date Wed, 11 Jun 2008 23:41:43 GMT
Author: dag
Date: Wed Jun 11 16:41:43 2008
New Revision: 666901

URL: http://svn.apache.org/viewvc?rev=666901&view=rev
Log:
DERBY-3678 StackOverflowException in deadlock trace

Patch derby-3678-3 which solves the issue and adds a new regression test.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/SYSTABLESRowFactory.java
    db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/LazyDefaultSchemaCreationTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=666901&r1=666900&r2=666901&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
Wed Jun 11 16:41:43 2008
@@ -350,6 +350,24 @@
 						throws StandardException;
 
 	/**
+	 * Get the SchemaDescriptor for the given schema identifier.
+	 *
+	 * @param schemaId	The id of the schema we're interested in.
+	 *
+	 * @param isolationLevel use this explicit isolation level
+	 * @param tc		The transaction controller to us when scanning
+	 *					SYSSCHEMAS
+	 *
+	 * @return	The descriptor for the schema, null if no such schema exists.
+	 *
+	 * @exception StandardException		Thrown on failure
+	 */
+	public SchemaDescriptor	getSchemaDescriptor(UUID schemaId,
+												int isolationLevel,
+												TransactionController tc)
+						throws StandardException;
+
+	/**
 	 * Return true of there exists a schema whose authorizationId
 	 * equals authid, i.e.  SYSSCHEMAS contains a row whose column
 	 * AUTHORIZATIONID equals authid.

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=666901&r1=666900&r2=666901&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
Wed Jun 11 16:41:43 2008
@@ -1504,6 +1504,47 @@
 								TransactionController tc)
 		throws StandardException
 	{
+		return locateSchemaRowBody(
+			schemaId,
+			TransactionController.ISOLATION_REPEATABLE_READ,
+			tc);
+	}
+
+
+	/**
+	 * Get the target schema by searching for a matching row
+	 * in SYSSCHEMAS by schemaId.  Read only scan.
+	 *
+	 * @param schemaId		The id of the schema we're interested in.
+	 *						If non-null, overrides schemaName
+	 * @param isolationLevel Use this explicit isolation level. Only
+	 *                      ISOLATION_REPEATABLE_READ (normal usage) or
+	 *                      ISOLATION_READ_UNCOMMITTED (corner cases)
+	 *                      supported for now.
+	 * @param tc			TransactionController.  If null, one
+	 *						is gotten off of the language connection context.
+	 *
+	 * @return	The row for the schema
+	 *
+	 * @exception StandardException		Thrown on error
+	 */
+	private SchemaDescriptor locateSchemaRow(UUID schemaId,
+											 int isolationLevel,
+											 TransactionController tc)
+		throws StandardException
+	{
+		return locateSchemaRowBody(
+			schemaId,
+			isolationLevel,
+			tc);
+	}
+
+
+	private SchemaDescriptor locateSchemaRowBody(UUID schemaId,
+												 int isolationLevel,
+												 TransactionController tc)
+		throws StandardException
+	{
 		DataValueDescriptor		UUIDStringOrderable;
 		TabInfoImpl					ti = coreInfo[SYSSCHEMAS_CORE_NUM];
 
@@ -1523,6 +1564,7 @@
 						(TupleDescriptor) null,
 						(List) null,
 						false,
+						isolationLevel,
 						tc);
 	}
 		
@@ -1585,6 +1627,40 @@
 									TransactionController tc)
 		throws StandardException
 	{
+		return getSchemaDescriptorBody(
+			schemaId,
+			TransactionController.ISOLATION_REPEATABLE_READ,
+			tc);
+	}
+
+	/**
+	 * Get the SchemaDescriptor for the given schema identifier.
+	 *
+	 * @param schemaId the uuid of the schema we want a descriptor for
+	 * @param isolationLevel use this explicit isolation level. Only
+	 *                       ISOLATION_REPEATABLE_READ (normal usage) or
+	 *                       ISOLATION_READ_UNCOMMITTED (corner cases)
+	 *                       supported for now.
+	 * @param tc transaction controller
+	 * @throws StandardException thrown on error
+	 */
+	public SchemaDescriptor	getSchemaDescriptor(UUID schemaId,
+												int isolationLevel,
+												TransactionController tc)
+		throws StandardException
+	{
+		return getSchemaDescriptorBody(
+			schemaId,
+			isolationLevel,
+			tc);
+	}
+
+
+	private SchemaDescriptor getSchemaDescriptorBody(
+		UUID schemaId,
+		int isolationLevel,
+		TransactionController tc) throws StandardException
+	{
 		SchemaDescriptor		sd = null;
 		
 		if ( tc == null )
@@ -1631,7 +1707,7 @@
 			}
 		}
 
-		return locateSchemaRow(schemaId, tc);
+		return locateSchemaRow(schemaId, isolationLevel, tc);
 	}
 
 
@@ -5811,9 +5887,10 @@
 	}
 
 	/**
-	 * Get all of the TableDescriptors in the database and hash them by TableId
-	 * This is useful as a performance optimization for the locking VTIs.
-	 * NOTE:  This method will scan SYS.SYSTABLES at READ UNCOMMITTED.
+	 * Get all of the TableDescriptors in the database and hash them
+	 * by TableId This is useful as a performance optimization for the
+	 * locking VTIs.  NOTE: This method will scan SYS.SYSTABLES and
+	 * SYS.SYSSCHEMAS at READ UNCOMMITTED.
 	 *
 	 * @param tc		TransactionController for the transaction
 	 *
@@ -5855,8 +5932,11 @@
 		while(scanController.fetchNext(outRow.getRowArray()))
 		{
 			TableDescriptor td = (TableDescriptor)
-				rf.buildDescriptor(outRow, (TupleDescriptor)null,
-								   this);
+				rf.buildDescriptor(
+					outRow,
+					(TupleDescriptor)null,
+					this,
+					TransactionController.ISOLATION_READ_UNCOMMITTED);
 			ht.put(td.getUUID(), td);
 		}
 		scanController.close();
@@ -8065,14 +8145,16 @@
 		// Get the current transaction controller
 		TransactionController tc = getTransactionCompile();
 
-		return getDescriptorViaIndexMinion(indexId,
-										   keyRow,
-										   scanQualifiers,
-										   ti,
-										   parentTupleDescriptor,
-										   list,
-										   forUpdate,
-										   tc);
+		return getDescriptorViaIndexMinion(
+			indexId,
+			keyRow,
+			scanQualifiers,
+			ti,
+			parentTupleDescriptor,
+			list,
+			forUpdate,
+			TransactionController.ISOLATION_REPEATABLE_READ,
+			tc);
 	}
 
 	/**
@@ -8110,6 +8192,62 @@
 			tc = getTransactionCompile();
 		}
 
+		return getDescriptorViaIndexMinion(
+			indexId,
+			keyRow,
+			scanQualifiers,
+			ti,
+			parentTupleDescriptor,
+			list,
+			forUpdate,
+			TransactionController.ISOLATION_REPEATABLE_READ,
+			tc);
+	}
+
+	/**
+	 * Return a (single or list of) catalog row descriptor(s) from a
+	 * system table where the access is from the index to the heap.
+	 *
+	 * This overload variant takes an explicit tc, in contrast to the normal
+	 * one which uses the one returned by getTransactionCompile.
+	 *
+	 * @param indexId   The id of the index (0 to # of indexes on table) to use
+	 * @param keyRow    The supplied ExecIndexRow for search
+	 * @param ti        The TabInfoImpl to use
+	 * @param parentTupleDescriptor The parentDescriptor, if applicable.
+	 * @param list      The list to build, if supplied.  If null, then
+	 *                  caller expects a single descriptor
+	 * @param forUpdate Whether or not to open the index for update.
+	 * @param isolationLevel
+	 *                  Use this explicit isolation level. Only
+	 *                  ISOLATION_REPEATABLE_READ (normal usage) or
+	 *                  ISOLATION_READ_UNCOMMITTED (corner cases)
+	 *                  supported for now.
+	 * @param tc        Transaction controller
+	 *
+	 * @return The last matching descriptor. If isolationLevel is
+	 *         ISOLATION_READ_UNCOMMITTED, the base row may be gone by the
+	 *         time we access it via the index; in such a case a null is
+	 *         returned.
+	 *
+	 * @exception StandardException Thrown on error.
+	 */
+	private final TupleDescriptor getDescriptorViaIndex(
+						int indexId,
+						ExecIndexRow keyRow,
+						ScanQualifier [][] scanQualifiers,
+						TabInfoImpl ti,
+						TupleDescriptor parentTupleDescriptor,
+						List list,
+						boolean forUpdate,
+						int isolationLevel,
+						TransactionController tc)
+			throws StandardException
+	{
+		if (tc == null) {
+			tc = getTransactionCompile();
+		}
+
 		return getDescriptorViaIndexMinion(indexId,
 										   keyRow,
 										   scanQualifiers,
@@ -8117,6 +8255,7 @@
 										   parentTupleDescriptor,
 										   list,
 										   forUpdate,
+										   isolationLevel,
 										   tc);
 	}
 
@@ -8129,6 +8268,7 @@
 						TupleDescriptor parentTupleDescriptor,
 						List list,
 						boolean forUpdate,
+						int isolationLevel,
 						TransactionController tc)
 			throws StandardException
 	{
@@ -8141,12 +8281,27 @@
 		ScanController			scanController;
 		TupleDescriptor			td = null;
 
+		if (SanityManager.DEBUG) {
+			SanityManager.ASSERT
+				(isolationLevel ==
+				 TransactionController.ISOLATION_REPEATABLE_READ ||
+				 isolationLevel ==
+				 TransactionController.ISOLATION_READ_UNCOMMITTED);
+
+			if (isolationLevel ==
+				 TransactionController.ISOLATION_READ_UNCOMMITTED) {
+				// list not used for this case
+				SanityManager.ASSERT(list == null);
+			}
+
+		}
+
 		outRow = rf.makeEmptyRow();
 
 		heapCC = tc.openConglomerate(
                 ti.getHeapConglomerate(), false, 0,
                 TransactionController.MODE_RECORD,
-                TransactionController.ISOLATION_REPEATABLE_READ);
+                isolationLevel);
 
 		/* Scan the index and go to the data pages for qualifying rows to
 		 * build the column descriptor.
@@ -8156,7 +8311,7 @@
 				false, // don't hold open across commit
 				(forUpdate) ? TransactionController.OPENMODE_FORUPDATE : 0,
                 TransactionController.MODE_RECORD,
-                TransactionController.ISOLATION_REPEATABLE_READ,
+                isolationLevel,
 				(FormatableBitSet) null,         // all fields as objects
 				keyRow.getRowArray(),   // start position - first row
 				ScanController.GE,      // startSearchOperation
@@ -8164,7 +8319,7 @@
 				keyRow.getRowArray(),   // stop position - through last row
 				ScanController.GT);     // stopSearchOperation
 
-		while (scanController.next())
+		while (true)
 		{
  			// create an index row template
 			indexRow1 = getIndexRowFromHeapRow(
@@ -8172,7 +8327,14 @@
 									heapCC.newRowLocationTemplate(),
 									outRow);
 
-			scanController.fetch(indexRow1.getRowArray());
+			// It is important for read uncommitted scans to use fetchNext()
+			// rather than fetch, so that the fetch happens while latch is
+			// held, otherwise the next() might position the scan on a row,
+			// but the subsequent fetch() may find the row deleted or purged
+			// from the table.
+			if (!scanController.fetchNext(indexRow1.getRowArray())) {
+				break;
+			}
 
 			baseRowLocation = (RowLocation)	indexRow1.getColumn(
 												indexRow1.nColumns());
@@ -8222,8 +8384,9 @@
             {
                 // it can not be possible for heap row to disappear while
                 // holding scan cursor on index at ISOLATION_REPEATABLE_READ.
-				if (! base_row_exists)
-				{
+				if (! base_row_exists &&
+						(isolationLevel ==
+							 TransactionController.ISOLATION_REPEATABLE_READ)) {
 					StringBuffer strbuf = new StringBuffer("Error retrieving base row in table "+ti.getTableName());
 					strbuf.append(": could not locate a row matching index row "+indexRow1+" from index
"+ti.getIndexName(indexId)+", conglom number "+ti.getIndexConglomerate(indexId));
                     debugGenerateInfo(strbuf,tc,heapCC,ti,indexId);
@@ -8236,7 +8399,41 @@
 				}
             }
 
-			td = rf.buildDescriptor(outRow, parentTupleDescriptor, this);
+			if (!base_row_exists &&
+					(isolationLevel ==
+						 TransactionController.ISOLATION_READ_UNCOMMITTED)) {
+				// If isolationLevel == ISOLATION_READ_UNCOMMITTED we may
+				// possibly see that the base row does not exist even if the
+				// index row did.  This mode is currently only used by
+				// TableNameInfo's call to hashAllTableDescriptorsByTableId,
+				// cf. DERBY-3678. A table's schema descriptor is attempted
+				// read, and if the base row for the schema has gone between
+				// reading the index and the base table, the table that needs
+				// this information has gone, too.  So, the table should not
+				// be needed for printing lock timeout or deadlock
+				// information, so we can safely just return an empty (schema)
+				// descriptor. Furthermore, neither Timeout or DeadLock
+				// diagnostics access the schema of a table descriptor, so it
+				// seems safe to just return an empty schema descriptor for
+				// the table.
+				//
+				// There is a theoretical chance another row may have taken
+				// the first one's place, but only if a compress of the base
+				// table managed to run between the time we read the index and
+				// the base row, which seems unlikely so we ignore that.
+				//
+				// Even the index row may be gone in the above use case, of
+				// course, and that case also returns an empty descriptor
+				// since no match is found.
+
+				td = null;
+
+			} else {
+				// normal case
+				td = rf.buildDescriptor(outRow, parentTupleDescriptor, this);
+			}
+
+
 
 			/* If list is null, then caller only wants a single descriptor - we're done
 			 * else just add the current descriptor to the list.

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/SYSTABLESRowFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/SYSTABLESRowFactory.java?rev=666901&r1=666900&r2=666901&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/SYSTABLESRowFactory.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/SYSTABLESRowFactory.java
Wed Jun 11 16:41:43 2008
@@ -48,6 +48,7 @@
 
 import org.apache.derby.catalog.UUID;
 import org.apache.derby.iapi.services.uuid.UUIDFactory;
+import org.apache.derby.iapi.store.access.TransactionController;
 
 /**
  * Factory for creating a SYSTABLES row.
@@ -256,6 +257,32 @@
 		return	row;
 	}
 
+	/**
+	 * Make a TableDescriptor out of a SYSTABLES row
+	 *
+	 * @param row a SYSTABLES row
+	 * @param parentTupleDescriptor	Null for this kind of descriptor.
+	 * @param dd dataDictionary
+	 * @param isolationLevel use this explicit isolation level. Only
+	 *                       ISOLATION_REPEATABLE_READ (normal usage)
+	 *                       or ISOLATION_READ_UNCOMMITTED (corner
+	 *                       cases) supported for now.
+	 * @exception   StandardException thrown on failure
+	 */
+	TupleDescriptor buildDescriptor(
+		ExecRow					row,
+		TupleDescriptor			parentTupleDescriptor,
+		DataDictionary 			dd,
+		int                     isolationLevel)
+					throws StandardException
+	{
+		return buildDescriptorBody(row,
+								   parentTupleDescriptor,
+								   dd,
+								   isolationLevel);
+	}
+
+
 	///////////////////////////////////////////////////////////////////////////
 	//
 	//	ABSTRACT METHODS TO BE IMPLEMENTED BY CHILDREN OF CatalogRowFactory
@@ -280,6 +307,21 @@
 		DataDictionary 			dd )
 					throws StandardException
 	{
+		return buildDescriptorBody(
+			row,
+			parentTupleDescriptor,
+			dd,
+			TransactionController.ISOLATION_REPEATABLE_READ);
+	}
+
+
+	public TupleDescriptor buildDescriptorBody(
+		ExecRow					row,
+		TupleDescriptor			parentTupleDescriptor,
+		DataDictionary 			dd,
+		int                     isolationLevel)
+					throws StandardException
+	{
 		if (SanityManager.DEBUG)
 		SanityManager.ASSERT(row.nColumns() == SYSTABLES_COLUMN_COUNT, "Wrong number of columns
for a SYSTABLES row");
 
@@ -338,7 +380,7 @@
 		schemaUUIDString = col.getString();
 		schemaUUID = getUUIDFactory().recreateUUID(schemaUUIDString);
 		
-		schema = dd.getSchemaDescriptor(schemaUUID, null);
+		schema = dd.getSchemaDescriptor(schemaUUID, isolationLevel, null);
 
 		/* 5th column is LOCKGRANULARITY (char(1)) */
 		col = row.getColumn(SYSTABLES_LOCKGRANULARITY);

Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=666901&r1=666900&r2=666901&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
(original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
Wed Jun 11 16:41:43 2008
@@ -135,6 +135,14 @@
 		return null;
 	}
 
+	public SchemaDescriptor	getSchemaDescriptor(UUID schemaId,
+												int isolationLevel,
+												TransactionController tc)
+			throws StandardException {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
 	public boolean existsSchemaOwnedBy(String authid,
 									   TransactionController tc)
 			throws StandardException {

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/LazyDefaultSchemaCreationTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/LazyDefaultSchemaCreationTest.java?rev=666901&r1=666900&r2=666901&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/LazyDefaultSchemaCreationTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/LazyDefaultSchemaCreationTest.java
Wed Jun 11 16:41:43 2008
@@ -157,11 +157,6 @@
         c1.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
         Statement s1 = c1.createStatement();
 
-        s1.executeUpdate(
-            "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(" +
-                "'derby.locks.deadlockTrace', 'true')");
-
-
         // Set read locks in parent transaction
         s1.executeQuery("select count(*) from sys.sysschemas");
 
@@ -182,6 +177,37 @@
         c1.rollback();
     }
 
+    /**
+     * Test that the timeout lock diagnostics do not create an
+     * infinite recursion as in DERBY-3678 (although that particular
+     * use case will not cause an infinite recursion after the fix to
+     * DERBY-48). The scenario in this test case does create the
+     * infinite recursion prior to the fix of DERBY-3678, however.
+     */
+    public void testDerby3678 ()
+            throws SQLException
+    {
+        Connection c1 = openUserConnection("newuser");
+        Connection c2 = null;
+
+        c1.setAutoCommit(false);
+        Statement s1 = c1.createStatement();
+
+        // set locks in connection 1:
+        s1.executeUpdate("create schema newuser");
+        s1.executeUpdate("create table t(i int)");
+
+        // ..which conflicts with the next connect
+        try {
+            c2 = openUserConnection("newuser");
+            fail("Expected exception " + LOCK_TIMEOUT_LOG);
+        } catch (SQLException e) {
+            assertSQLState("Expected state: ", LOCK_TIMEOUT_LOG, e);
+        } finally {
+            c1.rollback();
+        }
+    }
+
     protected void  tearDown() throws Exception {
         try {
             createStatement().executeUpdate("drop schema newuser restrict");
@@ -221,6 +247,15 @@
                   2,   // deadlock timeout
                   1)); // wait timeout
 
+            suites[i].addTest
+                (DatabasePropertyTestSetup.setLockTimeouts
+                 (new DatabasePropertyTestSetup
+                  (new LazyDefaultSchemaCreationTest
+                   ("testDerby3678"),
+                   p, false),
+                  2,   // deadlock timeout
+                  1)); // wait timeout
+
             if (i == 0) {
                 suite.addTest(suites[i]);
             } else {



Mime
View raw message