/*
* Copyright 2009 Udai Gupta, Ralf Joachim
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.castor.cpa.test.test04;
import java.util.Enumeration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.cpa.test.framework.CPATestCase;
import org.castor.cpa.test.framework.xml.types.DatabaseEngineType;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.mapping.AccessMode;
/**
* Tests for deadlock detection. Will report to the console two concurrent
* transactions working on the same objects. The first transaction will
* succeed, the second will fail. All three access modes: Shared (aka
* optimistic locking), Exclusive (aka pessimistic locking) and DbLocked
* (premissitic memory locking with DBMS's locking) are covered by these
* test cases. These tests passed if LockNotGrantedException is thrown
* in the appropriate time for the access mode in action.
*/
public final class TestDeadlock extends CPATestCase {
/** The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
* Commons Logging</a> instance used for all logging. */
private static final Log LOG = LogFactory.getLog(TestDeadlock.class);
/**
* The time a transaction sleep and wait for another transaction to
* process
*/
public static final long WAIT = 1000;
private static final String DBNAME = "test04";
private static final String MAPPING = "/org/castor/cpa/test/test04/mapping.xml";
private Database _db;
/** AccessMode used in the tests */
public static AccessMode _accessMode;
/** The java object to be synchronized on */
public static Object _lock;
/** The JDO database used for first concurrent transactions */
private Database _firstDb;
/** The thread that the first transaction is running on */
private FirstThread _firstThread;
public Exception _firstProblem;
/** The JDO database used for second concurrent transactions */
private Database _secondDb;
/** The thread that the second transaction is running on */
public static SecondThread _secondThread;
public Exception _secondProblem;
/**
* Constructor
*
* @param category The test suit of these test cases
*/
public TestDeadlock(final String name) {
super(name);
}
// Test are only included/excluded for engines that have been tested with this test suite.
//
// Temporarily disabled because of sporadic failures
public boolean include(final DatabaseEngineType engine) {
return false;
// return (engine == DatabaseEngineType.DERBY)
// || (engine == DatabaseEngineType.HSQL)
// || (engine == DatabaseEngineType.MYSQL)
// || (engine == DatabaseEngineType.ORACLE)
// || (engine == DatabaseEngineType.POSTGRESQL);
}
/**
* Get the JDO Databases and create data objects into the database
*/
public void setUp() throws Exception {
_db = getJDOManager(DBNAME, MAPPING).getDatabase();
_firstDb = getJDOManager(DBNAME, MAPPING).getDatabase();
_secondDb = getJDOManager(DBNAME, MAPPING).getDatabase();
Sample object;
Enumeration<?> enumeration;
// Open transaction in order to perform JDO operations
_db.begin();
// Create two objects in the database -- need something to lock
OQLQuery oql = _db.getOQLQuery("SELECT object FROM "
+ Sample.class.getName() + " object WHERE id = $1");
oql.bind(Sample.DEFAULT_ID);
enumeration = oql.execute();
if (enumeration.hasMoreElements()) {
object = (Sample) enumeration.nextElement();
LOG.debug("Retrieved object: " + object);
object.setValue1(Sample.DEFAULT_VALUE_1);
object.setValue2(Sample.DEFAULT_VALUE_2);
} else {
object = new Sample();
LOG.debug("Creating new object: " + object);
_db.create(object);
}
oql.bind(Sample.DEFAULT_ID + 1);
enumeration = oql.execute();
if (enumeration.hasMoreElements()) {
object = (Sample) enumeration.nextElement();
LOG.debug("Retrieved object: " + object);
object.setValue1(Sample.DEFAULT_VALUE_1);
object.setValue2(Sample.DEFAULT_VALUE_2);
} else {
object = new Sample();
object.setId(Sample.DEFAULT_ID + 1);
LOG.debug("Creating new object: " + object);
_db.create(object);
}
_db.commit();
}
/**
* Run tests for each of the three access modes.
* <p>
* Notice that some database have their own deadlock detection mechanisms
* implemented. Depending on the stricktness of the algorithm, when
* these tests are runing in DbLocked mode, some database might throw
* an exception to resolve the deadlock before Castor JDO detects it.
*/
public void testDeadlock() throws InterruptedException {
LOG.info("Running in access mode shared");
runOnce(Database.SHARED);
LOG.info("Running in access mode exclusive");
runOnce(Database.EXCLUSIVE);
LOG.info("Running in access mode db-locked");
runOnce(Database.DBLOCKED);
}
/**
* Close the JDO databases used in the these test cases.
*/
public void tearDown() throws PersistenceException {
if (_db.isActive()) { _db.rollback(); }
_db.close();
if (_firstDb.isActive()) { _firstDb.rollback(); }
_firstDb.close();
if (_secondDb.isActive()) { _secondDb.rollback(); }
_secondDb.close();
}
/**
* Creates threads to test for deadlock detection behaviors.
*/
public void runOnce(final AccessMode accessMode) throws InterruptedException {
LOG.debug("Note: this test uses a 2 seconds delay between "
+ "threads. CPU and database load might cause the test to not "
+ "perform synchronously, resulting in erroneous results. Make "
+ "sure that execution is not hampered by CPU/datebase load.");
// Run two threads in parallel attempting to update the
// two objects in a different order, with the first
// suceeding and second failing
_accessMode = accessMode;
_lock = new Object();
_firstThread = new FirstThread(_firstDb, this);
_firstProblem = null;
_secondThread = new SecondThread(_secondDb, this);
_secondProblem = null;
_secondThread.start();
_firstThread.start();
_firstThread.join();
_secondThread.join();
if (!_firstThread._resultOk || !_secondThread._resultOk) {
if (!_firstThread._resultOk) {
LOG.error("first failed: ", _firstProblem);
}
if (!_secondThread._resultOk) {
LOG.error("second failed: ", _secondProblem);
}
fail("unexpected deadlock behavior");
}
}
}