blob: 57aab3dd0fce28f0af10bd129fe68c35ef8a9bea [file] [log] [blame]
/*
* TestPessimisticLocking.java
*
* Created on October 13, 2006, 3:17 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.openjpa.persistence.kernel;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.common.utils.AbstractTestCase;
import org.apache.openjpa.persistence.jdbc.FetchMode;
import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
import org.apache.openjpa.persistence.kernel.common.apps.RuntimeTest1;
import org.apache.openjpa.persistence.kernel.common.apps.RuntimeTest2;
import org.apache.openjpa.persistence.kernel.common.apps.RuntimeTest3;
public class TestPessimisticLocking extends BaseKernelTest {
private Object _id = null;
private int _bugCount = 0;
private OpenJPAEntityManagerFactory _factory = null;
public TestPessimisticLocking(String name) {
super(name);
}
/**
* Creates a new instance of TestPessimisticLocking
*/
public TestPessimisticLocking() {
}
protected boolean skipTest() {
if (getConfiguration() instanceof JDBCConfiguration) {
JDBCConfiguration conf = (JDBCConfiguration) getConfiguration();
return !conf.getDBDictionaryInstance().supportsSelectForUpdate;
}
return false;
}
/**
* Use a locking persistence manager with subclass fetch mode to set to
* "none" to avoid subclass joins.
*/
protected OpenJPAEntityManager getLockingPM() {
OpenJPAEntityManager pm = _factory.createEntityManager();
((JDBCFetchPlan) pm.getFetchPlan())
.setSubclassFetchMode(FetchMode.NONE);
return pm;
}
@Override
public void setUp() throws Exception {
super.setUp(RuntimeTest1.class, RuntimeTest2.class, RuntimeTest3.class);
Map propsMap = new HashMap();
propsMap.put("openjpa.LockManager", "pessimistic");
_factory = getEmf(propsMap);
OpenJPAEntityManager pm = getLockingPM();
startTx(pm);
RuntimeTest1 a = new RuntimeTest1("name", 0);
pm.persist(a);
_id = pm.getObjectId(a);
endTx(pm);
endEm(pm);
}
@Override
public void tearDown() throws Exception {
try {
if (_factory != null) {
_factory.close();
_factory = null;
}
} catch (Exception e) {
}
super.tearDown();
}
/**
* Test that pessimistic locking is working in the data store.
*/
public void testPessimisticLocking() throws Throwable {
pessimisticLockingTest(false);
}
/**
* Test that the test case itself is working be using a ReentrantLock. This
* test will validate that the test case itself is working correctly, not
* that the datastore's pessimistic locking is working.
*/
public void testPessimisticLockingInternal() throws Throwable {
pessimisticLockingTest(true);
}
/**
* Test that pessimistic locking is working by attempting to update the same
* object in the data store.
*
* @param useReentrantLock true if we want to synchronize on a lock instead
* of relying on the data store (used for validating the test case).
*/
public void pessimisticLockingTest(boolean useReentrantLock)
throws Throwable {
if (skipTest()) {
return;
}
long timeout = System.currentTimeMillis() + (60 * 5 * 1000);
ReentrantLock lock = null;
if (useReentrantLock)
lock = new ReentrantLock();
TestThread t1 = new TestThread(lock);
TestThread t2 = new TestThread(lock);
t1.start();
t2.start();
getLog().trace("started thread");
// wait for threads to die or timeout
while ((t1.isAlive() || t2.isAlive())
&& System.currentTimeMillis() < timeout) {
Thread.sleep(1000);
getLog().trace(
"thread waiting for completion ("
+ (timeout - System.currentTimeMillis())
+ " ms left)");
}
getLog().trace("checking if thread is alive");
System.out.flush();
if (t1.isAlive() || t2.isAlive()) {
getLog().trace("thread is still alive");
System.out.flush();
// do out best to clean them up
try {
t1.interrupt();
}
catch (Exception e) {
}
try {
t2.interrupt();
}
catch (Exception e) {
}
throw new Exception("Thread did not complete after timeout ("
+ timeout + "): possible deadlock");
}
getLog().trace("checking exception for t1");
if (t1.exception != null)
throw t1.exception;
getLog().trace("checking exception for t2");
if (t2.exception != null)
throw t2.exception;
getLog().trace("verifying pessimistic locking worked...");
OpenJPAEntityManager pm = getLockingPM();
RuntimeTest1 a = pm.find(RuntimeTest1.class, _id);
assertEquals(20 - _bugCount, a.getIntField1());
getLog().trace("closing pm");
endEm(pm);
getLog().trace("done");
}
/**
* Update thread that tries to increment an int field.
*
* @author <a href="mailto:marc@solarmetric.com">Marc Prud'hommeaux</a>
*/
private class TestThread extends Thread {
private OpenJPAEntityManager _pm = getLockingPM();
public Exception exception = null;
private final ReentrantLock _lock;
/**
* Constructor
*
* @param lock the ReentrantLock we should use, or null to rely on
* pessimistic locking in the data store.
*/
public TestThread(ReentrantLock lock) {
this._lock = lock;
}
@Override
public synchronized void run() {
getLog().trace(
Thread.currentThread().getName()
+ ": starting update thread");
try {
for (int i = 0; i < 10; i++) {
if (_lock != null)
_lock.lock();
try {
_pm.setOptimistic(false);
startTx(_pm);
RuntimeTest1 a = (RuntimeTest1) _pm.find(
RuntimeTest1.class, _id);
getLog().trace(
Thread.currentThread().getName()
+ ": obtained and locked: " + a);
Thread.yield();
super.wait(50);
getLog().trace(
Thread.currentThread().getName()
+ ": updating age from "
+ a.getIntField1());
a.setIntField1(a.getIntField1() + 1);
getLog().trace(
Thread.currentThread().getName()
+ ": committed update");
try {
_pm.flush();
endTx(_pm);
}
catch (Exception ex) {
throw new org.apache.openjpa.util.UserException(
"Optimistic lock probably failed after "
+ i + " iterations ("
+ Thread.currentThread().getName()
+ ")", ex);
}
Thread.yield();
}
finally {
if (_lock != null)
_lock.unlock();
}
}
}
catch (Exception e) {
exception = e;
}
}
}
}