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 mi...@apache.org on 2012/02/01 19:01:33 UTC

svn commit: r1239239 - in /db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql: GenericActivationHolder.java GenericPreparedStatement.java GenericStatement.java

Author: mikem
Date: Wed Feb  1 18:01:33 2012
New Revision: 1239239

URL: http://svn.apache.org/viewvc?rev=1239239&view=rev
Log:
DERBY-5406 Intermittent failures in CompressTableTest and TruncateTableTest

backported #1175785 #1176256 #1187204 #1189067 #1190220 #1234776 changes from
trunk to 10.8 line.  Here is final comment from last change.


Retry compilation if it fails because a conglomerate has disappeared.
This may happen if DDL, compress, truncate or similar operations happen
while the statement is being compiled. When trying again, the compilation
should find the new conglomerate if one exists, or fail with a proper
error message if the SQL object has been removed.

This is a workaround for a race condition in the dependency management.
When binding a statement, the compiler typically builds descriptor
objects (like a TableDescriptor) from the system tables and then registers
the statement as a dependent on that descriptor. However, another thread
may at the same time be invalidating all dependents of that descriptor.
It is possible that this happens right before the current statement has
been registered as a dependent, and it will never see the invalidation
request. Once it actually tries to access the conglomerate associated with
the descriptor, it will fail with a "conglomerate does not exist" error,
and since the statement did not see the invalidation request, the compiler
doesn't know that it should retry the compilation.

This fix also backs out the changes made in revision 1187204, as they
addressed a subset of the cases handled by this broader fix, and are not
needed any more. 


Modified:
    db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
    db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java
    db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericStatement.java

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java?rev=1239239&r1=1239238&r2=1239239&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java Wed Feb  1 18:01:33 2012
@@ -261,23 +261,24 @@ final public class GenericActivationHold
 		{
 			/* Has the activation class changed or has the activation been
 			 * invalidated? */
-			if (gc != ps.getActivationClass() || !ac.isValid())
+            final boolean needNewClass =
+                    gc == null || gc != ps.getActivationClass();
+			if (needNewClass || !ac.isValid())
 			{
 
                 GeneratedClass newGC;
 
-				if (gc != ps.getActivationClass()) {
-					// ensure the statement is valid by rePreparing it.
-					// DERBY-3260: If someone else reprepares the statement at
-					// the same time as we do, there's a window between the
-					// calls to rePrepare() and getActivationClass() when the
-					// activation class can be set to null, leading to
-					// NullPointerException being thrown later. Therefore,
-					// synchronize on ps to close the window.
-					synchronized (ps) {
-						ps.rePrepare(getLanguageConnectionContext());
-						newGC = ps.getActivationClass();
-					}
+				if (needNewClass) {
+                    // The statement has been re-prepared since the last time
+                    // we executed it. Get the new activation class.
+                    newGC = ps.getActivationClass();
+                    if (newGC == null) {
+                        // There is no class associated with the statement.
+                        // Tell the caller that the statement needs to be
+                        // recompiled.
+                        throw StandardException.newException(
+                                SQLState.LANG_STATEMENT_NEEDS_RECOMPILE);
+                    }
 				} else {
 					// Reuse the generated class, we just want a new activation
 					// since the old is no longer valid.

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java?rev=1239239&r1=1239238&r2=1239239&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericPreparedStatement.java Wed Feb  1 18:01:33 2012
@@ -147,6 +147,8 @@ public class GenericPreparedStatement
 	// true if the statement is being compiled.
 	boolean compilingStatement;
 
+    /** True if the statement was invalidated while it was being compiled. */
+    boolean invalidatedWhileCompiling;
 
 	////////////////////////////////////////////////
 	// STATE that is not copied by getClone()
@@ -406,7 +408,7 @@ recompileOutOfDatePlan:
 			// to execute.  That exception will be caught by the executeSPS()
 			// method of the GenericTriggerExecutor class, and at that time
 			// the SPS action will be recompiled correctly.
-				rePrepare(lccToUse);
+                rePrepare(lccToUse);
 			}
 
 			StatementContext statementContext = lccToUse.pushStatementContext(
@@ -785,7 +787,16 @@ recompileOutOfDatePlan:
 		synchronized (this) {
 
 			if (compilingStatement)
+            {
+                // Since the statement is in the process of being compiled,
+                // and at the end of the compilation it will set isValid to
+                // true and overwrite whatever we set it to here, set another
+                // flag to indicate that an invalidation was requested. A
+                // re-compilation will be triggered if this flag is set, but
+                // not until the current compilation is done.
+                invalidatedWhileCompiling = true;
 				return;
+            }
 
 			alreadyInvalid = !isValid;
 		

Modified: db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericStatement.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericStatement.java?rev=1239239&r1=1239238&r2=1239239&view=diff
==============================================================================
--- db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericStatement.java (original)
+++ db/derby/code/branches/10.8/java/engine/org/apache/derby/impl/sql/GenericStatement.java Wed Feb  1 18:01:33 2012
@@ -82,7 +82,7 @@ public class GenericStatement
 		** Note: don't reset state since this might be
 		** a recompilation of an already prepared statement.
 		*/ 
-		return prepMinion(lcc, true, (Object[]) null, (SchemaDescriptor) null, false); 
+		return prepare(lcc, false);
 	}
 	public PreparedStatement prepare(LanguageConnectionContext lcc, boolean forMetaData) throws StandardException
 	{
@@ -90,7 +90,79 @@ public class GenericStatement
 		** Note: don't reset state since this might be
 		** a recompilation of an already prepared statement.
 		*/ 
-		return prepMinion(lcc, true, (Object[]) null, (SchemaDescriptor) null, forMetaData); 
+
+        final int depth = lcc.getStatementDepth();
+        String prevErrorId = null;
+        while (true) {
+            boolean recompile = false;
+            try {
+                return prepMinion(lcc, true, (Object[]) null,
+                                  (SchemaDescriptor) null, forMetaData);
+            } catch (StandardException se) {
+                // There is a chance that we didn't see the invalidation
+                // request from a DDL operation in another thread because
+                // the statement wasn't registered as a dependent until
+                // after the invalidation had been completed. Assume that's
+                // what has happened if we see a conglomerate does not exist
+                // error, and force a retry even if the statement hasn't been
+                // invalidated.
+                if (SQLState.STORE_CONGLOMERATE_DOES_NOT_EXIST.equals(
+                        se.getMessageId())) {
+                    // STORE_CONGLOMERATE_DOES_NOT_EXIST has exactly one
+                    // argument: the conglomerate id
+                    String conglomId = String.valueOf(se.getArguments()[0]);
+
+                    // Request a recompile of the statement if a conglomerate
+                    // disappears while we are compiling it. But if we have
+                    // already retried once because the same conglomerate was
+                    // missing, there's probably no hope that yet another retry
+                    // will help, so let's break out instead of potentially
+                    // looping infinitely.
+                    if (!conglomId.equals(prevErrorId)) {
+                        recompile = true;
+                    }
+
+                    prevErrorId = conglomId;
+                }
+                throw se;
+            } finally {
+                // Check if the statement was invalidated while it was
+                // compiled. If so, the newly compiled plan may not be
+                // up to date anymore, so we recompile the statement
+                // if this happens. Note that this is checked in a finally
+                // block, so we also retry if an exception was thrown. The
+                // exception was probably thrown because of the changes
+                // that invalidated the statement. If not, recompiling
+                // will also fail, and the exception will be exposed to
+                // the caller.
+                //
+                // invalidatedWhileCompiling and isValid are protected by
+                // synchronization on the prepared statement.
+                synchronized (preparedStmt) {
+                    if (recompile || preparedStmt.invalidatedWhileCompiling) {
+                        preparedStmt.isValid = false;
+                        preparedStmt.invalidatedWhileCompiling = false;
+                        recompile = true;
+                    }
+                }
+
+                if (recompile) {
+                    // A new statement context is pushed while compiling.
+                    // Typically, this context is popped by an error
+                    // handler at a higher level. But since we retry the
+                    // compilation, the error handler won't be invoked, so
+                    // the stack must be reset to its original state first.
+                    while (lcc.getStatementDepth() > depth) {
+                        lcc.popStatementContext(
+                                lcc.getStatementContext(), null);
+                    }
+
+                    // Don't return yet. The statement was invalidated, so
+                    // we must retry the compilation.
+                    continue;
+                }
+            }
+        }
 	}
 
 	private PreparedStatement prepMinion(LanguageConnectionContext lcc, boolean cacheMe, Object[] paramDefaults,