[Mulgara-svn] r208 - trunk/src/jar/resolver/java/org/mulgara/resolver

andrae at mulgara.org andrae at mulgara.org
Thu Mar 22 08:18:33 UTC 2007


Author: andrae
Date: 2007-03-22 02:18:32 -0600 (Thu, 22 Mar 2007)
New Revision: 208

Modified:
   trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
Log:
Adds a test fo AdvDatabaseSessionUnitTest that checks that delete/select implicitly commits.
This is a repeatable test for MGR-48 (http://mulgara.org/jira/browse/MGR-48) "update results
incorrect when autocommit on and multiple sessions are used".

We retain the patch provided by Ronald, as this bug was triggered by the delete operation
retaining a reference to the write-transaction until the garbarge-collector collected the
result of the inner select.  The result was a failure to commit, and therefore incorrect
results with concurrent sessions.  By closing the temporary answer, the reference count
was corrected and the transaction properly committed and terminated.

To ensure that this cannot happen again, several alternatives were considered that would have
provided an explicit call to 'commit' at the end of the delete.  These were rejected as this
would have required DatabaseSession to either track or query the auto-commit status to determine
if the transaction should be committed.  This would also bypass the reference counting 
maintained by the transaction object itself, and reintroduce transaction-handling logic into
DatabaseSession after this had been delegated to the 3 transaction management classes.

The ultimate solution chosen was to track the autocommit status explicitly within the manager, 
and to check this status when a transaction attempts to suspend.  The error we need to detect
is a write-transaction outliving an operation that is supposed to be atomic.  The only way a
transaction can outlive _any_ operation is to be suspended, so checking for validity on suspend
will guarantee this type of error can never occur.  The result will now be a failed atomic write
operation, something that will be highly visible and easily fixed.



Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2007-03-19 07:01:14 UTC (rev 207)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2007-03-22 08:18:32 UTC (rev 208)
@@ -78,6 +78,7 @@
   private static final URI modelURI;
   private static final URI model2URI;
   private static final URI model3URI;
+  private static final URI model4URI;
 
   static {
     try {
@@ -86,6 +87,7 @@
       modelURI       = new URI("local:database#model");
       model2URI      = new URI("local:database#model2");
       model3URI      = new URI("local:database#model3");
+      model4URI      = new URI("local:database#model4");
     } catch (URISyntaxException e) {
       throw new Error("Bad hardcoded URI", e);
     }
@@ -111,6 +113,7 @@
     suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitIsolationQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitRollbackIsolationQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitCommitIsolationQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testImplicitCommitQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testDatabaseDelete"));
 
     return suite;
@@ -759,7 +762,6 @@
     }
   }
 
-
   public void testExplicitRollbackIsolationQuery() throws URISyntaxException
   {
     logger.info("testExplicitRollbackIsolationQuery");
@@ -1013,6 +1015,109 @@
   }
 
 
+  public void testImplicitCommitQuery() throws URISyntaxException
+  {
+    logger.info("testImplicitCommitQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          session1.createModel(model4URI, null);
+          session1.setModel(model4URI, new ModelResource(fileURI));
+
+          Variable subjectVariable   = new Variable("subject");
+          Variable predicateVariable = new Variable("predicate");
+          Variable objectVariable    = new Variable("object");
+
+          List selectList = new ArrayList(3);
+          selectList.add(subjectVariable);
+          selectList.add(predicateVariable);
+          selectList.add(objectVariable);
+
+          // Check data loaded
+          Answer answer = session1.query(new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model4URI),                      // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            Arrays.asList(new Order[] {                       // ORDER BY
+              new Order(subjectVariable, true),
+              new Order(predicateVariable, true),
+              new Order(objectVariable, true)
+            }),
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+
+          String[][] results = {
+            { "test:s01", "test:p01", "test:o01" },
+            { "test:s01", "test:p02", "test:o01" },
+            { "test:s01", "test:p02", "test:o02" },
+            { "test:s01", "test:p03", "test:o02" },
+            { "test:s02", "test:p03", "test:o02" },
+            { "test:s02", "test:p04", "test:o02" },
+            { "test:s02", "test:p04", "test:o03" },
+            { "test:s02", "test:p05", "test:o03" },
+            { "test:s03", "test:p01", "test:o01" },
+            { "test:s03", "test:p05", "test:o03" },
+            { "test:s03", "test:p06", "test:o01" },
+            { "test:s03", "test:p06", "test:o03" },
+          };
+          compareResults(results, answer);
+          answer.close();
+
+          // Delete all the data from the model
+          session2.delete(model4URI, new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model4URI),                     // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            new ArrayList(),                                  // ORDER BY
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+
+          // Check all data removed from the model
+          // This also checks that the delete successfully
+          // performed the implicit commit.
+          answer = session1.query(new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model4URI),                      // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            new ArrayList(),                                  // ORDER BY
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+
+          session1.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
   public void testDatabaseDelete() {
     database.delete();
     database = null;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2007-03-19 07:01:14 UTC (rev 207)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2007-03-22 08:18:32 UTC (rev 208)
@@ -73,6 +73,7 @@
   // Write lock is associated with a session.
   private Session currentWritingSession;
   private MulgaraTransaction userTransaction;
+  private boolean autoCommit;
 
   /** Set of sessions whose transactions have been rolledback.*/
   private Set<Session> failedSessions;
@@ -98,6 +99,7 @@
   public MulgaraTransactionManager(TransactionManagerFactory transactionManagerFactory) {
     this.currentWritingSession = null;
     this.userTransaction = null;
+    this.autoCommit = true;
 
     this.failedSessions = new HashSet<Session>();
     this.sessions = new HashMap<MulgaraTransaction, Session>();
@@ -240,6 +242,7 @@
             });
           } finally {
             releaseWriteLock();
+            this.autoCommit = true;
           }
         } else if (failedSessions.contains(session)) {
           // Within failed transaction - cleanup.
@@ -257,6 +260,7 @@
         // AutoCommit on -> off == Start new transaction.
         userTransaction = getTransaction(session, true);
         userTransaction.reference();
+        this.autoCommit = false;
       }
     }
   }
@@ -400,7 +404,18 @@
             "Attempt to suspend transaction from outside thread");
       }
 
-      return transactionManager.suspend();
+      if (autoCommit && transaction == userTransaction) {
+        logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
+        throw new MulgaraTransactionException(
+            "Attempt to suspend write transaction without setting AutoCommit Off");
+      } else {
+        logger.error("Suspended transaction: ac=" + autoCommit + " t=" + transaction + "ut=" + userTransaction);
+      }
+
+      Transaction xa = transactionManager.suspend();
+      activeTransactions.remove(Thread.currentThread());
+
+      return xa;
     } catch (Throwable th) {
       logger.error("Attempt to suspend failed", th);
       try {
@@ -409,8 +424,6 @@
         logger.error("Attempt to setRollbackOnly() failed", t);
       }
       throw new MulgaraTransactionException("Suspend failed", th);
-    } finally {
-      activeTransactions.remove(Thread.currentThread());
     }
   }
 




More information about the Mulgara-svn mailing list