OPENJPA-1809 Modified syncVersion exception path to pass the lock level into the exception producer. This eventually allows the exception translator to correctly decide whether the exception was the result of a lock timeout.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@999559 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jeremy Bauer 2010-09-21 19:27:08 +00:00
parent b76ab3884e
commit 9b105a2f00
3 changed files with 202 additions and 2 deletions

View File

@ -305,10 +305,18 @@ public class JDBCStoreManager
try {
return mapping.getVersion().checkVersion(sm, this, true);
} catch (SQLException se) {
throw SQLExceptions.getStore(se, _dict);
throw SQLExceptions.getStore(se, _dict, getReadLockLevel());
}
}
private int getReadLockLevel() {
JDBCFetchConfiguration fetch = getFetchConfiguration();
if (fetch != null) {
return fetch.getReadLockLevel();
}
return -1;
}
public int compareVersion(OpenJPAStateManager state, Object v1, Object v2) {
ClassMapping mapping = (ClassMapping) state.getMetaData();
return mapping.getVersion().compareVersion(v1, v2);

View File

@ -21,6 +21,11 @@ package org.apache.openjpa.persistence.lockmgr;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
@ -32,7 +37,10 @@ import javax.persistence.TypedQuery;
import junit.framework.AssertionFailedError;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DB2Dictionary;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.DerbyDictionary;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.persistence.LockTimeoutException;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
@ -65,7 +73,7 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
if (isTestsDisabled())
return;
setUp(CLEAR_TABLES, Employee.class, Department.class, "openjpa.LockManager", "mixed");
setUp(CLEAR_TABLES, Employee.class, Department.class, VersionEntity.class, "openjpa.LockManager", "mixed");
EntityManager em = null;
em = emf.createEntityManager();
@ -399,6 +407,82 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
em.getTransaction().commit();
}
protected Log getLog() {
return emf.getConfiguration().getLog("Tests");
}
/**
* This variation introduces a row level write lock in a secondary thread,
* issues a refresh in the main thread with a lock timeout, and expects a
* LockTimeoutException.
*/
public void testRefreshLockTimeout() {
// Only run this test on DB2 and Derby for now. It could cause
// the test to hang on other platforms.
if (!(dict instanceof DerbyDictionary ||
dict instanceof DB2Dictionary)) {
return;
}
EntityManager em = emf.createEntityManager();
resetSQL();
VersionEntity ve = new VersionEntity();
int veid = new Random().nextInt();
ve.setId(veid);
ve.setName("Versioned Entity");
em.getTransaction().begin();
em.persist(ve);
em.getTransaction().commit();
em.getTransaction().begin();
// Assert that the department can be found and no lock mode is set
ve = em.find(VersionEntity.class, veid);
assertTrue(em.contains(ve));
assertTrue(em.getLockMode(ve) == LockModeType.NONE);
em.getTransaction().commit();
// Kick of a thread to lock the DB for update
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Boolean> result = executor.submit(new RefreshWithLock(veid, this));
try {
// Wait for the thread to lock the row
getLog().trace("Main: waiting");
synchronized (this) {
// The derby lock timeout is configured for 60 seconds, by default.
wait(70000);
}
getLog().trace("Main: done waiting");
Map<String,Object> props = new HashMap<String,Object>();
// This property does not have any effect on Derby for the locking
// condition produced by this test. Instead, Derby uses the
// lock timeout value specified in the config (pom.xml)
props.put("javax.persistence.lock.timeout", 5000);
em.getTransaction().begin();
getLog().trace("Main: refresh with force increment");
em.refresh(ve, LockModeType.PESSIMISTIC_FORCE_INCREMENT, props);
getLog().trace("Main: commit");
em.getTransaction().commit();
getLog().trace("Main: done commit");
fail("Expected LockTimeoutException");
} catch (Throwable t) {
getLog().trace("Main: exception - " + t.getMessage(), t);
assertTrue( t instanceof LockTimeoutException);
} finally {
try {
// Wake the thread and wait for the thread to finish
synchronized(this) {
this.notify();
}
result.get();
} catch (Throwable t) {
fail("Caught throwable waiting for thread finish: " + t);
}
}
}
/**
* Assert that an exception of proper type has been thrown. Also checks that
* that the exception has populated the failed object.
@ -435,4 +519,51 @@ public class TestPessimisticLocks extends SQLListenerTestCase {
return null;
}
/**
* Separate execution thread used to forcing a lock condition on
* a row in the VersionEntity table.
*/
public class RefreshWithLock implements Callable<Boolean> {
private int _id;
private Object _monitor;
public RefreshWithLock(int id, Object monitor) {
_id = id;
_monitor = monitor;
}
public Boolean call() throws Exception {
try {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// Find with pessimistic force increment. Will lock row for duration of TX.
VersionEntity ve = em.find(VersionEntity.class, _id, LockModeType.PESSIMISTIC_FORCE_INCREMENT);
assertTrue(em.getLockMode(ve) == LockModeType.PESSIMISTIC_FORCE_INCREMENT);
// Wake up the main thread
getLog().trace("Thread: wake up main thread");
synchronized(_monitor) {
_monitor.notify();
}
// Wait up to 120 seconds for main thread to complete. The default derby timeout is 60 seconds.
try {
getLog().trace("Thread: waiting up to 120 secs for notify");
synchronized(_monitor) {
_monitor.wait(120000);
}
getLog().trace("Thread: done waiting");
} catch (Throwable t) {
getLog().trace("Unexpected thread interrupt",t);
}
em.getTransaction().commit();
em.close();
getLog().trace("Thread: done");
} catch (Throwable t) {
getLog().trace("Thread: caught - " + t.getMessage(), t);
}
return Boolean.TRUE;
}
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.lockmgr;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name="LK_VERSENT")
public class VersionEntity {
@Id
private int id;
private String name;
@Version
private int version;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
}