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