OPENJPA-1943 - Apply query timeout value to pessimistic row lock statement execution.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1072061 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Albert Lee 2011-02-18 16:59:16 +00:00
parent cf4a80047e
commit 0c37fc6e57
10 changed files with 207 additions and 95 deletions

View File

@ -47,7 +47,7 @@
</dictionary> </dictionary>
<dictionary class="org.apache.openjpa.jdbc.sql.SQLServerDictionary"> <dictionary class="org.apache.openjpa.jdbc.sql.SQLServerDictionary">
<lock>1204,1205,1222,HY008</lock> <lock>1204,1205,1222,HY008,40001</lock>
<referential-integrity>544,2601,2627,8114,8115</referential-integrity> <referential-integrity>544,2601,2627,8114,8115</referential-integrity>
<object-exists>23000</object-exists> <object-exists>23000</object-exists>
<object-not-found></object-not-found> <object-not-found></object-not-found>

View File

@ -455,10 +455,14 @@ public class BrokerImpl
} }
public FetchConfiguration pushFetchConfiguration() { public FetchConfiguration pushFetchConfiguration() {
return pushFetchConfiguration(null);
}
public FetchConfiguration pushFetchConfiguration(FetchConfiguration fc) {
if (_fcs == null) if (_fcs == null)
_fcs = new LinkedList<FetchConfiguration>(); _fcs = new LinkedList<FetchConfiguration>();
_fcs.add(_fc); _fcs.add(_fc);
_fc = (FetchConfiguration) _fc.clone(); _fc = (FetchConfiguration) (fc != null ? fc : _fc).clone();
return _fc; return _fc;
} }

View File

@ -154,6 +154,14 @@ public class DelegatingBroker
} }
} }
public FetchConfiguration pushFetchConfiguration(FetchConfiguration fc) {
try {
return _broker.pushFetchConfiguration(fc);
} catch (RuntimeException re) {
throw translate(re);
}
}
public void popFetchConfiguration() { public void popFetchConfiguration() {
try { try {
_broker.popFetchConfiguration(); _broker.popFetchConfiguration();

View File

@ -73,6 +73,15 @@ public interface StoreContext {
*/ */
public FetchConfiguration pushFetchConfiguration(); public FetchConfiguration pushFetchConfiguration();
/**
* Pushes the fetch configuration argument onto a stack, and makes the new configuration
* the active one.
*
* @since 2.1.1
* @return the new fetch configuration
*/
public FetchConfiguration pushFetchConfiguration(FetchConfiguration fc);
/** /**
* Pops the fetch configuration from the top of the stack, making the * Pops the fetch configuration from the top of the stack, making the
* next one down the active one. This returns void to avoid confusion, * next one down the active one. This returns void to avoid confusion,

View File

@ -271,6 +271,12 @@ public abstract class AbstractPersistenceTestCase extends TestCase {
for (Broker b : ((AbstractBrokerFactory) JPAFacadeHelper.toBrokerFactory(emf)).getOpenBrokers()) { for (Broker b : ((AbstractBrokerFactory) JPAFacadeHelper.toBrokerFactory(emf)).getOpenBrokers()) {
if (b != null && !b.isClosed()) { if (b != null && !b.isClosed()) {
EntityManager em = JPAFacadeHelper.toEntityManager(b); EntityManager em = JPAFacadeHelper.toEntityManager(b);
if( em.getTransaction().isActive() ) {
try {
em.getTransaction().rollback();
} catch (Exception e) {
}
}
closeEM(em); closeEM(em);
} }
} }

View File

@ -684,41 +684,93 @@ public abstract class SequencedActionsTest extends SQLListenerTestCase {
} }
} }
} }
String testExClass = null;
Throwable curThrowable = null;
int threadId = threadToRun; int threadId = threadToRun;
if (args.length > 1) { if (args.length > 1) {
threadId = (Integer) args[1]; threadId = (Integer) args[1];
} }
TestThread exThread = threads.get(threadId); if( threadId != -1 ) {
curThrowable = exThread.throwable; // test exception on a specific thread
testExClass = processException(curAction, curThrowable); String testExClass = null;
Throwable curThrowable = null;
boolean exMatched = false;
TestThread exThread = threads.get(threadId);
curThrowable = exThread.throwable;
testExClass = processException(exThread, curAction, curThrowable);
boolean exMatched = false; if (expectedExceptions != null
if (expectedExceptions != null && expectedExceptions.size() > 0) {
&& expectedExceptions.size() > 0) { for (Class<?> expectedException :
for (Class<?> expectedException : expectedExceptions) {
expectedExceptions) { if (matchExpectedException(curAct, expectedException,
if (matchExpectedException(curAct, expectedException, curThrowable)) {
curThrowable)) { exMatched = true;
break;
}
}
} else {
if (curThrowable == null) {
exMatched = true; exMatched = true;
break;
} }
} }
if (!exMatched) {
log.trace(testExClass);
if (curThrowable != null) {
logStack(curThrowable);
}
}
assertTrue(curAct + ":Expecting=" + expectedExceptions
+ ", Testing=" + testExClass, exMatched);
exThread.throwable = null;
} else { } else {
if (curThrowable == null) { // test exception in any thread; used for deadlock exception testing since db server
exMatched = true; // decides on which thread to terminate if deadlock is detected.
} if (expectedExceptions == null || expectedExceptions.size() == 0) {
// Expecting no exception in all threads.
boolean noExMatched = true;
String aTestExClass = "[";
for (TestThread aThread : threads) {
Throwable aThrowable = aThread.throwable;
aTestExClass += processException(aThread, curAction, aThrowable) + ", ";
if (aThrowable != null) {
noExMatched = false;
log.trace(aTestExClass);
logStack(aThrowable);
aThread.throwable = null;
}
}
assertTrue(curAct + ":Expecting=[no exception]"
+ ", Testing=" + aTestExClass + ']', noExMatched);
} else {
// Expecting any exception in any threads.
boolean aMatched = false;
String aTestExClass = "[";
for (TestThread aThread : threads) {
Throwable aThrowable = aThread.throwable;
aTestExClass += processException(aThread, curAction, aThrowable) + ", ";
for (Class<?> anExpectedException : expectedExceptions) {
if (matchExpectedException(curAct,
anExpectedException, aThrowable)) {
aMatched = true;
break;
}
}
if (aMatched) {
break;
} else {
if (aThrowable != null) {
logStack(aThrowable);
aThread.throwable = null;
}
}
}
if (!aMatched) {
log.trace(aTestExClass);
}
assertTrue(curAct + ":Expecting=" + expectedExceptions
+ ", Testing=" + aTestExClass + "]", aMatched);
}
} }
if (!exMatched) {
log.trace(testExClass);
if (curThrowable != null) {
logStack(curThrowable);
}
}
assertTrue(curAct + ":Expecting=" + expectedExceptions
+ ", Testing=" + testExClass, exMatched);
exThread.throwable = null;
break; break;
case WaitAllChildren: case WaitAllChildren:
@ -855,7 +907,7 @@ public abstract class SequencedActionsTest extends SQLListenerTestCase {
log.trace("finally: commit completed"); log.trace("finally: commit completed");
} }
} catch(Exception finalEx) { } catch(Exception finalEx) {
String failStr = processException(curAction, finalEx); String failStr = processException(thisThread, curAction, finalEx);
log.trace("Fincally:" + failStr); log.trace("Fincally:" + failStr);
} }
} }
@ -881,11 +933,11 @@ public abstract class SequencedActionsTest extends SQLListenerTestCase {
return lockMode; return lockMode;
} }
private String processException(Act curAction, Throwable t) { private String processException(TestThread thread, Act curAction, Throwable t) {
String failStr = "Caught exception: none"; String failStr = "[" + thread.threadToRun + "] Caught exception: none";
if (t != null) { if (t != null) {
getLog().trace( getLog().trace(
"Caught exception: " + t.getClass().getName() + ":" + t); "[" + thread.threadToRun + "] Caught exception: " + t.getClass().getName() + ":" + t);
logStack(t); logStack(t);
Throwable rootCause = t.getCause(); Throwable rootCause = t.getCause();
failStr = "Failed on action '" + curAction + "' with exception " failStr = "Failed on action '" + curAction + "' with exception "

View File

@ -19,6 +19,7 @@
package org.apache.openjpa.persistence.lockmgr; package org.apache.openjpa.persistence.lockmgr;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
@ -28,12 +29,14 @@ import javax.persistence.LockModeType;
*/ */
public class TestMixedLockManagerDeadlock extends SequencedActionsTest { public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
private DBType dbType; private DBType dbType;
private HashMap<DBType,Class<?>[]> expWriteLockExClasses;
public void setUp() { public void setUp() {
setSupportedDatabases( setSupportedDatabases(
org.apache.openjpa.jdbc.sql.DB2Dictionary.class,
org.apache.openjpa.jdbc.sql.DerbyDictionary.class, org.apache.openjpa.jdbc.sql.DerbyDictionary.class,
org.apache.openjpa.jdbc.sql.OracleDictionary.class, org.apache.openjpa.jdbc.sql.OracleDictionary.class,
org.apache.openjpa.jdbc.sql.DB2Dictionary.class); org.apache.openjpa.jdbc.sql.SQLServerDictionary.class);
if (isTestsDisabled()) { if (isTestsDisabled()) {
return; return;
} }
@ -41,6 +44,12 @@ public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
setUp(LockEmployee.class setUp(LockEmployee.class
, "openjpa.LockManager", "mixed" , "openjpa.LockManager", "mixed"
); );
expWriteLockExClasses = new HashMap<DBType,Class<?>[]>();
expWriteLockExClasses.put(DBType.db2, null);
expWriteLockExClasses.put(DBType.derby, ExpectingOptimisticLockExClass);
expWriteLockExClasses.put(DBType.oracle, null);
expWriteLockExClasses.put(DBType.sqlserver, ExpectingOptimisticLockExClass);
commonSetUp(); commonSetUp();
EntityManager em = emf.createEntityManager(); EntityManager em = emf.createEntityManager();
dbType = getDBType(em); dbType = getDBType(em);
@ -49,8 +58,7 @@ public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
/* ======== Find dead lock exception test ============*/ /* ======== Find dead lock exception test ============*/
public void testFindDeadLockException() { public void testFindDeadLockException() {
commonFindTest("testFindDeadLockException", LockModeType.READ, null); commonFindTest("testFindDeadLockException", LockModeType.READ, null);
commonFindTest("testFindDeadLockException", LockModeType.WRITE, dbType == DBType.oracle ? null commonFindTest("testFindDeadLockException", LockModeType.WRITE, expWriteLockExClasses.get(dbType));
: ExpectingOptimisticLockExClass);
commonFindTest("testFindDeadLockException", LockModeType.PESSIMISTIC_WRITE, ExpectingAnyLockExClass); commonFindTest("testFindDeadLockException", LockModeType.PESSIMISTIC_WRITE, ExpectingAnyLockExClass);
} }
@ -73,7 +81,7 @@ public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
{Act.FindWithLock, 2, t1Lock}, {Act.FindWithLock, 2, t1Lock},
{Act.WaitAllChildren}, {Act.WaitAllChildren},
{Act.TestException, 1, t1Exceptions}, {Act.TestException, -1, t1Exceptions}, // test t1Exceptions in any thread
{Act.RollbackTx} {Act.RollbackTx}
}; };
Object[][] thread1 = { Object[][] thread1 = {
@ -94,10 +102,8 @@ public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
/* ======== named query dead lock exception test ============*/ /* ======== named query dead lock exception test ============*/
public void testNamedQueryDeadLockException() { public void testNamedQueryDeadLockException() {
commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.READ, null); commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.READ, null);
commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.WRITE, dbType == DBType.oracle ? null commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.WRITE, expWriteLockExClasses.get(dbType));
: ExpectingOptimisticLockExClass); commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.PESSIMISTIC_FORCE_INCREMENT, ExpectingAnyLockExClass);
// commonNamedQueryTest("testNamedQueryDeadLockException", LockModeType.PESSIMISTIC_FORCE_INCREMENT,
// ExpectingAnyLockExClass);
} }
private void commonNamedQueryTest( String testName, private void commonNamedQueryTest( String testName,
@ -119,7 +125,7 @@ public class TestMixedLockManagerDeadlock extends SequencedActionsTest {
{Act.NamedQueryWithLock, "findEmployeeById", 2, t1Lock, "openjpa.hint.IgnorePreparedQuery", true}, {Act.NamedQueryWithLock, "findEmployeeById", 2, t1Lock, "openjpa.hint.IgnorePreparedQuery", true},
{Act.WaitAllChildren}, {Act.WaitAllChildren},
{Act.TestException, 1, t1Exceptions}, {Act.TestException, -1, t1Exceptions},
{Act.RollbackTx}, {Act.RollbackTx},
{Act.CloseEm} {Act.CloseEm}

View File

@ -137,10 +137,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
em2.find(Employee.class, 2, LockModeType.PESSIMISTIC_READ, hints); em2.find(Employee.class, 2, LockModeType.PESSIMISTIC_READ, hints);
fail("Unexcpected find succeeded. Should throw a PessimisticLockException."); fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
} catch (Throwable e) { } catch (Throwable e) {
if (!dict.supportsLockingWithMultipleTables) assertError(e, PessimisticLockException.class, LockTimeoutException.class);
assertError(e, PessimisticLockException.class);
else
assertError(e, LockTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -206,10 +203,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
em2.find(Employee.class, 2, LockModeType.PESSIMISTIC_READ, map); em2.find(Employee.class, 2, LockModeType.PESSIMISTIC_READ, map);
fail("Unexcpected find succeeded. Should throw a PessimisticLockException."); fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
} catch (Exception e) { } catch (Exception e) {
if (!dict.supportsLockingWithMultipleTables) assertError(e, PessimisticLockException.class, LockTimeoutException.class);
assertError(e, PessimisticLockException.class);
else
assertError(e, LockTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -276,10 +270,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
assertTrue("Test department name = 'D20'", q.get(0).getName().equals("D10") assertTrue("Test department name = 'D20'", q.get(0).getName().equals("D10")
|| q.get(0).getName().equals("D20")); || q.get(0).getName().equals("D20"));
} catch (Exception ex) { } catch (Exception ex) {
if (!dict.supportsLockingWithMultipleTables) assertError(ex, QueryTimeoutException.class);
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
else
assertError(ex, QueryTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -304,10 +295,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
List<Employee> q = query.getResultList(); List<Employee> q = query.getResultList();
fail("Unexcpected find succeeded. Should throw a PessimisticLockException."); fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
} catch (Exception e) { } catch (Exception e) {
if (!dict.supportsLockingWithMultipleTables) assertError(e, PessimisticLockException.class, QueryTimeoutException.class);
assertError(e, PessimisticLockException.class);
else
assertError(e, QueryTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -342,10 +330,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
assertEquals("Expected 1 element with department id=20", q.size(), 1); assertEquals("Expected 1 element with department id=20", q.size(), 1);
assertEquals("Test department name = 'D20'", q.get(0).getName(), "D20"); assertEquals("Test department name = 'D20'", q.get(0).getName(), "D20");
} catch (Exception ex) { } catch (Exception ex) {
if (!dict.supportsLockingWithMultipleTables) assertError(ex, QueryTimeoutException.class);
fail("Caught unexpected " + ex.getClass().getName() + ":" + ex.getMessage());
else
assertError(ex, QueryTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -370,10 +355,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
List<?> q = query.getResultList(); List<?> q = query.getResultList();
fail("Unexcpected find succeeded. Should throw a PessimisticLockException."); fail("Unexcpected find succeeded. Should throw a PessimisticLockException.");
} catch (Exception e) { } catch (Exception e) {
if (!dict.supportsLockingWithMultipleTables) assertError(e, PessimisticLockException.class, QueryTimeoutException.class);
assertError(e, PessimisticLockException.class);
else
assertError(e, QueryTimeoutException.class);
} finally { } finally {
if (em1.getTransaction().isActive()) if (em1.getTransaction().isActive())
em1.getTransaction().rollback(); em1.getTransaction().rollback();
@ -505,12 +487,22 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
* @param expeceted * @param expeceted
* type of the exception * type of the exception
*/ */
void assertError(Throwable actual, Class<? extends Throwable> expected) { void assertError(Throwable actual, Class<? extends Throwable> ... expected) {
if (!expected.isAssignableFrom(actual.getClass())) { boolean matched = false;
actual.printStackTrace(); String expectedNames = "";
throw new AssertionFailedError(actual.getClass().getName() + " was raised but expected " for (Class<? extends Throwable> aExpected : expected) {
+ expected.getName()); expectedNames += aExpected.getName() + ", ";
} if (aExpected.isAssignableFrom(actual.getClass())) {
matched = true;
}
}
if (!matched) {
actual.printStackTrace();
throw new AssertionFailedError(actual.getClass().getName()
+ " was raised but expecting one of the following: ["
+ expectedNames.substring(0, expectedNames.length() - 2) + "]");
}
Object failed = getFailedObject(actual); Object failed = getFailedObject(actual);
assertNotNull("Failed object is null", failed); assertNotNull("Failed object is null", failed);
assertNotEquals("null", failed); assertNotEquals("null", failed);

View File

@ -163,10 +163,14 @@ public class EntityManagerImpl
} }
public FetchPlan pushFetchPlan() { public FetchPlan pushFetchPlan() {
return pushFetchPlan(null);
}
public FetchPlan pushFetchPlan(FetchConfiguration fc) {
assertNotCloseInvoked(); assertNotCloseInvoked();
_broker.lock(); _broker.lock();
try { try {
_broker.pushFetchConfiguration(); _broker.pushFetchConfiguration(fc);
return getFetchPlan(); return getFetchPlan();
} finally { } finally {
_broker.unlock(); _broker.unlock();

View File

@ -316,22 +316,26 @@ public class QueryImpl<X> implements OpenJPAQuerySPI<X>, Serializable {
public List getResultList() { public List getResultList() {
_em.assertNotCloseInvoked(); _em.assertNotCloseInvoked();
Object ob = execute(); boolean queryFetchPlanUsed = pushQueryFetchPlan();
if (ob instanceof List) { try {
List ret = (List) ob; Object ob = execute();
if (ret instanceof ResultList) { if (ob instanceof List) {
RuntimeExceptionTranslator trans = PersistenceExceptions.getRollbackTranslator(_em); List ret = (List) ob;
if (_query.isDistinct()) { if (ret instanceof ResultList) {
return new DistinctResultList((ResultList) ret, trans); RuntimeExceptionTranslator trans = PersistenceExceptions.getRollbackTranslator(_em);
if (_query.isDistinct()) {
return new DistinctResultList((ResultList) ret, trans);
} else {
return new DelegatingResultList((ResultList) ret, trans);
}
} else { } else {
return new DelegatingResultList((ResultList) ret, trans); return ret;
} }
} else { }
return ret; return Collections.singletonList(ob);
} } finally {
popQueryFetchPlan(queryFetchPlanUsed);
} }
return Collections.singletonList(ob);
} }
/** /**
@ -340,18 +344,45 @@ public class QueryImpl<X> implements OpenJPAQuerySPI<X>, Serializable {
public X getSingleResult() { public X getSingleResult() {
_em.assertNotCloseInvoked(); _em.assertNotCloseInvoked();
setHint(QueryHints.HINT_RESULT_COUNT, 1); // for DB2 optimization setHint(QueryHints.HINT_RESULT_COUNT, 1); // for DB2 optimization
List result = getResultList(); boolean queryFetchPlanUsed = pushQueryFetchPlan();
if (result == null || result.isEmpty())
throw new NoResultException(_loc.get("no-result", getQueryString())
.getMessage());
if (result.size() > 1)
throw new NonUniqueResultException(_loc.get("non-unique-result",
getQueryString(), result.size()).getMessage());
try { try {
return (X)result.get(0); List result = getResultList();
} catch (Exception e) { if (result == null || result.isEmpty())
throw new NoResultException(_loc.get("no-result", getQueryString()) throw new NoResultException(_loc.get("no-result", getQueryString())
.getMessage()); .getMessage());
if (result.size() > 1)
throw new NonUniqueResultException(_loc.get("non-unique-result",
getQueryString(), result.size()).getMessage());
try {
return (X)result.get(0);
} catch (Exception e) {
throw new NoResultException(_loc.get("no-result", getQueryString())
.getMessage());
}
} finally {
popQueryFetchPlan(queryFetchPlanUsed);
}
}
private boolean pushQueryFetchPlan() {
boolean fcPushed = false;
if (_fetch != null && _hintHandler != null) {
switch (_fetch.getReadLockMode()) {
case PESSIMISTIC_READ:
case PESSIMISTIC_WRITE:
case PESSIMISTIC_FORCE_INCREMENT:
// push query fetch plan to em if pessisimistic lock and any
// hints are set
_em.pushFetchPlan(((FetchPlanImpl)_fetch).getDelegate());
fcPushed = true;
}
}
return fcPushed;
}
private void popQueryFetchPlan(boolean queryFetchPlanUsed) {
if (queryFetchPlanUsed) {
_em.popFetchPlan();
} }
} }