Author: curtisr7
Date: Thu Aug 12 20:15:41 2010
New Revision: 984943

URL: http://svn.apache.org/viewvc?rev=984943&view=rev
Log:
OPENJPA-1765: Add locking to TableJDBCSeq to ensure thread safety.

Added:
    
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java
   (with props)
Modified:
    
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java

Modified: 
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java
URL: 
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java?rev=984943&r1=984942&r2=984943&view=diff
==============================================================================
--- 
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java
 (original)
+++ 
openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/TableJDBCSeq.java
 Thu Aug 12 20:15:41 2010
@@ -24,15 +24,15 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.transaction.NotSupportedException;
 
 import org.apache.commons.lang.StringUtils;
 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
 import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
-import org.apache.openjpa.jdbc.identifier.Normalizer;
 import org.apache.openjpa.jdbc.identifier.DBIdentifier;
+import org.apache.openjpa.jdbc.identifier.Normalizer;
 import org.apache.openjpa.jdbc.identifier.QualifiedDBIdentifier;
 import org.apache.openjpa.jdbc.identifier.DBIdentifier.DBIdentifierType;
 import org.apache.openjpa.jdbc.meta.ClassMapping;
@@ -71,9 +71,7 @@ import org.apache.openjpa.util.UserExcep
  *
  * @author Abe White
  */
-public class TableJDBCSeq
-    extends AbstractJDBCSeq
-    implements Configurable {
+public class TableJDBCSeq extends AbstractJDBCSeq implements Configurable {
 
     public static final String ACTION_DROP = "drop";
     public static final String ACTION_ADD = "add";
@@ -88,8 +86,7 @@ public class TableJDBCSeq
     private transient Log _log = null;
     private int _alloc = 50;
     private int _intValue = 1;
-    private final HashMap<ClassMapping, Status> _stat =
-        new HashMap<ClassMapping, Status>();
+    private final ConcurrentHashMap<ClassMapping, Status> _stat = new 
ConcurrentHashMap<ClassMapping, Status>();
 
     private DBIdentifier _table = DBIdentifier.newTable(DEFAULT_TABLE);
     private DBIdentifier _seqColumnName = 
DBIdentifier.newColumn("SEQUENCE_VALUE");
@@ -286,8 +283,7 @@ public class TableJDBCSeq
         }
     }
 
-    protected Object nextInternal(JDBCStore store, ClassMapping mapping)
-        throws Exception {
+    protected Object nextInternal(JDBCStore store, ClassMapping mapping) 
throws Exception {
         // if needed, grab the next handful of ids
         Status stat = getStatus(mapping);
         if (stat == null)
@@ -301,8 +297,8 @@ public class TableJDBCSeq
                 stat.seq = Math.max(stat.seq, 1);
                 if (stat.seq < stat.max)
                     return stat.seq++;
+                allocateSequence(store, mapping, stat, _alloc, true);
             }
-            allocateSequence(store, mapping, stat, _alloc, true);
         }
     }
 
@@ -350,10 +346,15 @@ public class TableJDBCSeq
      * if cannot handle the given class. The mapping may be null.
      */
     protected Status getStatus(ClassMapping mapping) {  
-        Status status = (Status)_stat.get(mapping);        
+        Status status = (Status) _stat.get(mapping);        
         if (status == null){ 
             status = new Status();
-            _stat.put(mapping, status);
+            Status tStatus = _stat.putIfAbsent(mapping, status);
+            // This can happen if another thread calls .put(..) sometime after 
our call to get. Return
+            // the value from the putIfAbsent call as that is truly in the map.
+            if (tStatus != null) {
+                return tStatus;
+            }
         }
         return status;
     }

Added: 
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java
URL: 
http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java?rev=984943&view=auto
==============================================================================
--- 
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java
 (added)
+++ 
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java
 Thu Aug 12 20:15:41 2010
@@ -0,0 +1,120 @@
+/*
+ * 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.generationtype;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.openjpa.jdbc.kernel.TableJDBCSeq;
+import org.apache.openjpa.kernel.StoreContext;
+import org.apache.openjpa.meta.ClassMetaData;
+import org.apache.openjpa.meta.MetaDataRepository;
+import org.apache.openjpa.meta.SequenceMetaData;
+import org.apache.openjpa.persistence.EntityManagerImpl;
+import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+public class TestTableGeneratorMultithreaded extends SingleEMFTestCase {
+    public final int ALLOC_SIZE = 100000;
+    public final int INITIAL = 1;
+    public final int LOOPS = 100000;
+    public final int THREADS = 5;
+
+    public void setUp() {
+        setUp(Dog.class, CLEAR_TABLES
+        // , "openjpa.Log", "SQL=trace", 
"openjpa.ConnectionFactoryProperties","PrintParameters=true"
+        );
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        MetaDataRepository repo = 
emf.getConfiguration().getMetaDataRepositoryInstance();
+        // Initialize MetaData
+        repo.getMetaData(Dog.class, loader, true);
+        repo.getSequenceMetaData("Dog_Gen", loader, true);
+
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testAllIdsAreUsed() throws Exception {
+        List<WorkerThread> threads = new ArrayList<WorkerThread>();
+        for (int i = 0; i < THREADS; i++) {
+            threads.add(new WorkerThread(emf));
+        }
+        for (Thread t : threads) {
+            t.start();
+        }
+        long max = 0;
+        for (WorkerThread w : threads) {
+            w.join();
+            max = Math.max(max, (Long) w.getLast());
+        }
+        assertEquals((LOOPS * (THREADS)), max);
+    }
+
+    class WorkerThread extends Thread {
+        Object _first = null, _last = null;
+        int _count = 0;
+        StoreContext _ctx;
+        TableJDBCSeq _seq;
+        ClassMetaData _cmd;
+        EntityManagerImpl _em;
+        MetaDataRepository _repo;
+
+        public WorkerThread(OpenJPAEntityManagerFactorySPI emf) {
+            _repo = emf.getConfiguration().getMetaDataRepositoryInstance();
+        }
+
+        public void run() {
+            ClassLoader contextLoader = 
Thread.currentThread().getContextClassLoader();
+            SequenceMetaData meta = _repo.getSequenceMetaData("Dog_Gen", 
contextLoader, true);
+            meta.setInitialValue(1);
+            meta.setIncrement(10000);
+            _em = (EntityManagerImpl) emf.createEntityManager();
+            _ctx = (StoreContext) _em.getBroker();
+            _cmd = _repo.getMetaData(Dog.class, contextLoader, true);
+
+            _seq = (TableJDBCSeq) meta.getInstance(contextLoader);
+            // Change defaults so this test doesn't take so long to run.
+            _seq.setAllocate(ALLOC_SIZE);
+            _seq.setInitialValue(1);
+
+            Object obj = _seq.next(_ctx, _cmd);
+            _first = obj;
+            // start at 1 because we already got our first result.
+            for (int i = 1; i < LOOPS; i++) {
+                obj = _seq.next(_ctx, _cmd);
+            }
+            _last = obj;
+            _em.close();
+        }
+
+        public Object getLast() {
+            return _last;
+        }
+
+        public Object getFirst() {
+            return _first;
+        }
+
+        public int getCount() {
+            return _count;
+        }
+    }// end WorkerThread
+}

Propchange: 
openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestTableGeneratorMultithreaded.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to