You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by ht...@apache.org on 2015/01/14 21:25:08 UTC

svn commit: r1651808 - in /openjpa/branches/2.2.1.x: openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/ openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/

Author: hthomann
Date: Wed Jan 14 20:25:07 2015
New Revision: 1651808

URL: http://svn.apache.org/r1651808
Log:
OPENJPA-2547: When two threads attempt to get a Pessimistic Lock, one thread gets a 'false' lock.  Applied fix to 2.2.1.x.

Added:
    openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/PessimisticLockEntity.java
    openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/TestPessimisticLockException.java
Modified:
    openjpa/branches/2.2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java

Modified: openjpa/branches/2.2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java?rev=1651808&r1=1651807&r2=1651808&view=diff
==============================================================================
--- openjpa/branches/2.2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java (original)
+++ openjpa/branches/2.2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/PessimisticLockManager.java Wed Jan 14 20:25:07 2015
@@ -34,6 +34,7 @@ import org.apache.openjpa.jdbc.sql.DBDic
 import org.apache.openjpa.jdbc.sql.SQLBuffer;
 import org.apache.openjpa.jdbc.sql.SQLFactory;
 import org.apache.openjpa.jdbc.sql.Select;
+import org.apache.openjpa.kernel.LockLevels;
 import org.apache.openjpa.kernel.LockScopes;
 import org.apache.openjpa.kernel.MixedLockLevels;
 import org.apache.openjpa.kernel.OpenJPAStateManager;
@@ -127,7 +128,12 @@ public class PessimisticLockManager
         Object id = sm.getObjectId();
         ClassMapping mapping = (ClassMapping) sm.getMetaData();
 
-        List<SQLBuffer> sqls = sm.getLock() == null
+        //Code changed for OPENJPA-2449, code updated for OPENJPA-2547.  OPENJPA-2547 added
+        //one check to determine if the lock is a value of LockLevels.LOCK_NONE.  The first 
+        //time a thread attempts to get a lock the lock will be null.  If the thread can't 
+        //get the lock because another thread holds it, the lock will be non-null and have 
+        //a value of LockLevels.LOCK_NONE.
+        List<SQLBuffer> sqls = (sm.getLock() == null || sm.getLock().equals(LockLevels.LOCK_NONE))
             ?  getLockRows(dict, id, mapping, fetch, _store.getSQLFactory())
             : new ArrayList<SQLBuffer>();
         if (ctx.getFetchConfiguration().getLockScope() == LockScopes.LOCKSCOPE_EXTENDED)

Added: openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/PessimisticLockEntity.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/PessimisticLockEntity.java?rev=1651808&view=auto
==============================================================================
--- openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/PessimisticLockEntity.java (added)
+++ openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/PessimisticLockEntity.java Wed Jan 14 20:25:07 2015
@@ -0,0 +1,47 @@
+/*
+ * 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 javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class PessimisticLockEntity {
+
+    @Id
+    int id;
+    
+    String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+}

Added: openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/TestPessimisticLockException.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/TestPessimisticLockException.java?rev=1651808&view=auto
==============================================================================
--- openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/TestPessimisticLockException.java (added)
+++ openjpa/branches/2.2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/kernel/TestPessimisticLockException.java Wed Jan 14 20:25:07 2015
@@ -0,0 +1,186 @@
+/*
+ * 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 javax.persistence.EntityManager;
+import javax.persistence.LockModeType;
+
+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.OracleDictionary;
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
+import org.apache.openjpa.persistence.OpenJPAPersistence;
+import org.apache.openjpa.persistence.PessimisticLockException;
+import org.apache.openjpa.persistence.test.SQLListenerTestCase;
+
+public class TestPessimisticLockException extends SQLListenerTestCase {
+    int pKey = 1;
+    static boolean doSleep = true;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp(PessimisticLockEntity.class);
+    }
+    
+    /*
+     * This test has only been verified on DB2 and Oracle.
+     */
+    protected boolean skipTest() {
+        if (emf.getConfiguration() instanceof JDBCConfiguration) {
+            DBDictionary inst = ((JDBCConfiguration) emf.getConfiguration()).getDBDictionaryInstance();
+            return !((inst instanceof DB2Dictionary) || (inst instanceof OracleDictionary));
+        }
+        return true;
+    }
+
+    /*
+     * This test will verify that two threads get a an appropriate pessimistic lock
+     * when they both request one at the same time.  See JIRA OPENJPA-2547 for a more
+     * detailed description of this test.
+     */
+    public void testPessimisticLockException() {
+        if (!skipTest()) {
+
+            populate();
+
+            TestThread t1 = new TestThread();
+            TestThread t2 = new TestThread();
+            t1.start();
+            t2.start();
+
+            while ((t1.isAlive() || t2.isAlive())) {
+                try {
+                    Thread.sleep(5000);
+                } catch (InterruptedException e) {
+                }
+            }
+
+            // One, and only one, thread should get a PersistenceException
+            if (t1.gotPLEx && t2.gotPLEx) {
+                fail("Both threads got a PersistenceLockException!  "
+                    + "Only one thread should have received a PersistenceLockException");
+            } else if (t1.gotPLEx == false && t2.gotPLEx == false) {
+                fail("Neither thread got a PersistenceLockException!  "
+                    + "One thread should have received a PersistenceLockException");
+            } else if (t1.count < 2 && t2.count < 2) {
+                fail("PersistenceLockException was received, but not the expected number of times!  "
+                    + "One thread should have received a PersistenceLockException at least twice.");
+            }
+        }
+    }
+
+    private class TestThread extends Thread {
+        boolean gotPLEx = false;
+        int count = 0;
+
+        public synchronized void run() {
+            OpenJPAEntityManager oem = OpenJPAPersistence.cast(emf.createEntityManager());
+            oem.getTransaction().begin();
+
+            PessimisticLockEntity entity = oem.find(PessimisticLockEntity.class, pKey);
+
+            boolean locked = false;
+            while (!locked) {
+                try {
+                    oem.getFetchPlan().setLockTimeout(5000);
+                    oem.lock(entity, LockModeType.PESSIMISTIC_READ);
+                    locked = true;
+                } catch (PessimisticLockException ple) {
+                    gotPLEx = true;
+                    count++;
+
+                    try {
+                        Thread.sleep(100);
+                    } catch (final InterruptedException ie) {
+                    }
+                    oem.refresh(entity);
+                } catch (Throwable pe) {
+                    pe.printStackTrace();
+                    fail("Caught an unexepected exception: " + pe);
+                }
+
+                // Only one thread needs to sleep (don't care about synchronization of 'doSleep' at this 
+                // point - if both threads happen to get here at the same time we will test for that later.)
+                if (doSleep) {
+                    doSleep = false;
+                    try {
+                        // Sleep log enough to ensure the other thread times out at least two times.
+                        Thread.sleep(15000);
+                    } catch (final InterruptedException ie) {
+                    }
+                }
+
+                if (!oem.getTransaction().getRollbackOnly()) {
+                    oem.getTransaction().commit();
+                }
+            }
+        }
+    }
+    
+    /*
+     * This test verifies the correct number of SQL statements when using a pessimistic 
+     * lock (See JIRA OPENJPA-2449).  Prior to OPENJPA-2449, when requesting a pessimistic lock
+     * we would do a 'select' to get the entity, and turn around and do another select to get a 
+     * Pessimistic lock...in other words, we'd generate (on DB2) these two SQL statements for the refresh:
+     * 
+     * SELECT t0.name FROM PessimisticLockEntity t0 WHERE t0.id = ?
+     * SELECT t0.name FROM PessimisticLockEntity t0 WHERE t0.id = ?  FOR READ ONLY WITH RR USE AND KEEP UPDATE LOCKS
+     *     
+     * With the fix of OPENJPA-2449, we generate only one select, as follows:
+     * 
+     * SELECT t0.name FROM PessimisticLockEntity t0 WHERE t0.id = ?  FOR READ ONLY WITH RR USE AND KEEP UPDATE LOCKS
+     * 
+     * Not only does this save an SQL, but more importantly, the few millisecond delay between the two selects
+     * won't occur.....in a multi-threaded env this delay could cause another thread to get the lock over this
+     * one when the refresh occurs at the same time.
+     */
+    public void testSQLCount() {
+        if (!skipTest()) {
+
+            populate();
+            resetSQL();
+
+            EntityManager em = emf.createEntityManager();
+            em.getTransaction().begin();
+
+            PessimisticLockEntity plEnt = em.find(PessimisticLockEntity.class, pKey);
+
+            em.refresh(plEnt, LockModeType.PESSIMISTIC_WRITE);
+
+            plEnt.setName("test");
+            em.getTransaction().commit();
+            em.close();
+            assertEquals("There should only be 3 SQL statements", 3, getSQLCount());
+        }
+    }
+
+    public void populate() {
+        EntityManager em = emf.createEntityManager();
+        em.getTransaction().begin();
+
+        PessimisticLockEntity pt = new PessimisticLockEntity();
+        pt.setId(pKey);
+
+        em.persist(pt);
+
+        em.getTransaction().commit();
+        em.close();
+    }
+}