/* * 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"); } } }