svn commit: r1668267 - in /ofbiz/trunk/framework/entity/src/org/ofbiz/entity: cache/Cache.java cache/EntityCache.java transaction/TransactionListener.java transaction/TransactionUtil.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1668267 - in /ofbiz/trunk/framework/entity/src/org/ofbiz/entity: cache/Cache.java cache/EntityCache.java transaction/TransactionListener.java transaction/TransactionUtil.java

adrianc
Author: adrianc
Date: Sat Mar 21 13:44:55 2015
New Revision: 1668267

URL: http://svn.apache.org/r1668267
Log:
Initial commit of the fix for the dirty cache reads problem - https://issues.apache.org/jira/browse/OFBIZ-5534.

This fixes the PK cache only, the other caches will be fixed after this commit is reviewed.

I didn't include a unit test because Derby deadlocks while testing for this. Actually, the deadlock proves the problem is fixed. I will continue trying to find a way to provide a unit test.

Added:
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionListener.java
Modified:
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/Cache.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java
    ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionUtil.java

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/Cache.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/Cache.java?rev=1668267&r1=1668266&r2=1668267&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/Cache.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/Cache.java Sat Mar 21 13:44:55 2015
@@ -23,25 +23,24 @@ import java.util.List;
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.UtilGenerics;
 import org.ofbiz.entity.GenericEntity;
-import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.GenericPK;
+import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.transaction.TransactionUtil;
 
 public class Cache {
 
     public static final String module = Cache.class.getName();
 
-    protected EntityCache entityCache;
-    protected EntityListCache entityListCache;
-    protected EntityObjectCache entityObjectCache;
-
-    protected String delegatorName;
+    private final EntityCache entityCache;
+    private final EntityListCache entityListCache;
+    private final EntityObjectCache entityObjectCache;
 
     public Cache(String delegatorName) {
-        this.delegatorName = delegatorName;
         entityCache = new EntityCache(delegatorName);
-        entityObjectCache = new EntityObjectCache(delegatorName);
         entityListCache = new EntityListCache(delegatorName);
+        entityObjectCache = new EntityObjectCache(delegatorName);
+        TransactionUtil.addListener(entityCache);
     }
 
     public void clear() {

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java?rev=1668267&r1=1668266&r2=1668267&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/cache/EntityCache.java Sat Mar 21 13:44:55 2015
@@ -18,7 +18,12 @@
  *******************************************************************************/
 package org.ofbiz.entity.cache;
 
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.transaction.UserTransaction;
 
 import org.ofbiz.base.util.Debug;
 import org.ofbiz.base.util.cache.UtilCache;
@@ -26,15 +31,34 @@ import org.ofbiz.entity.GenericPK;
 import org.ofbiz.entity.GenericValue;
 import org.ofbiz.entity.condition.EntityCondition;
 import org.ofbiz.entity.model.ModelEntity;
+import org.ofbiz.entity.transaction.GenericTransactionException;
+import org.ofbiz.entity.transaction.TransactionFactoryLoader;
+import org.ofbiz.entity.transaction.TransactionListener;
+import org.ofbiz.entity.transaction.TransactionUtil;
 
-public class EntityCache extends AbstractCache<GenericPK, GenericValue> {
+public class EntityCache extends AbstractCache<GenericPK, GenericValue> implements TransactionListener {
     public static final String module = EntityCache.class.getName();
+    private final ConcurrentHashMap<UserTransaction, Map<GenericPK, GenericValue>> txCacheMap = new ConcurrentHashMap<UserTransaction, Map<GenericPK, GenericValue>>();
 
     public EntityCache(String delegatorName) {
         super(delegatorName, "entity");
     }
 
     public GenericValue get(GenericPK pk) {
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.get(tx);
+                if (tempCache != null) {
+                    GenericValue value = tempCache.get(pk);
+                    if (value != null) {
+                        return value;
+                    }
+                }
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
         UtilCache<GenericPK, GenericValue> entityCache = getCache(pk.getEntityName());
         if (entityCache == null) return null;
         return entityCache.get(pk);
@@ -50,18 +74,46 @@ public class EntityCache extends Abstrac
             Debug.logWarning("Tried to put a value of the " + pk.getEntityName() + " entity in the BY PRIMARY KEY cache but this entity has never-cache set to true, not caching.", module);
             return null;
         }
-
+        pk.setImmutable();
         if (entity == null) {
             entity = GenericValue.NULL_VALUE;
         } else {
             // before going into the cache, make this value immutable
             entity.setImmutable();
         }
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.get(tx);
+                if (tempCache != null) {
+                    return tempCache.put(pk, entity);
+                }
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
         UtilCache<GenericPK, GenericValue> entityCache = getOrCreateCache(pk.getEntityName());
         return entityCache.put(pk, entity);
     }
 
     public void remove(String entityName, EntityCondition condition) {
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.get(tx);
+                if (tempCache != null) {
+                    for (Map.Entry<GenericPK, GenericValue> entry : tempCache.entrySet()) {
+                        GenericPK pk = entry.getKey();
+                        GenericValue value = entry.getValue();
+                        if (condition.entityMatches(value)) {
+                            tempCache.remove(pk);
+                        }
+                    }
+                }
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
         UtilCache<GenericPK, GenericValue> entityCache = getCache(entityName);
         if (entityCache == null) return;
         for (GenericPK pk: entityCache.getCacheLineKeys()) {
@@ -76,6 +128,17 @@ public class EntityCache extends Abstrac
     }
 
     public GenericValue remove(GenericPK pk) {
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.get(tx);
+                if (tempCache != null) {
+                    return tempCache.remove(pk);
+                }
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
         UtilCache<GenericPK, GenericValue> entityCache = getCache(pk.getEntityName());
         if (Debug.verboseOn()) Debug.logVerbose("Removing from EntityCache with PK [" + pk + "], will remove from this cache: " + (entityCache == null ? "[No cache found to remove from]" : entityCache.getName()), module);
         if (entityCache == null) return null;
@@ -91,4 +154,59 @@ public class EntityCache extends Abstrac
         if (Debug.verboseOn()) Debug.logVerbose("Removing from EntityCache with PK [" + pk + "], found this in the cache: " + retVal, module);
         return retVal;
     }
+
+    @Override
+    public void clear() {
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                txCacheMap.remove(tx);
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
+        super.clear();
+    }
+
+    @Override
+    public void remove(String entityName) {
+        try {
+            if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
+                UserTransaction tx = TransactionFactoryLoader.getInstance().getUserTransaction();
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.get(tx);
+                if (tempCache != null) {
+                    for (GenericPK pk : tempCache.keySet()) {
+                        if (pk.getEntityName().equals(entityName)) {
+                            tempCache.remove(pk);
+                        }
+                    }
+                }
+            }
+        } catch (GenericTransactionException e) {
+            Debug.logWarning(e, "Exception thrown while getting transaction status: ", module);
+        }
+        super.remove(entityName);
+    }
+
+    @Override
+    public void update(UserTransaction tx, EventType notificationType) {
+        switch (notificationType) {
+            case BEGIN:
+                txCacheMap.put(tx, new HashMap<GenericPK, GenericValue>());
+                break;
+            case COMMIT:
+                Map<GenericPK, GenericValue> tempCache = txCacheMap.remove(tx);
+                if (tempCache != null) {
+                    for (Map.Entry<GenericPK, GenericValue> entry : tempCache.entrySet()) {
+                        GenericPK pk = entry.getKey();
+                        GenericValue value = entry.getValue();
+                        UtilCache<GenericPK, GenericValue> entityCache = getOrCreateCache(pk.getEntityName());
+                        entityCache.put(pk, value);
+                    }
+                }
+                break;
+            case ROLLBACK:
+                txCacheMap.remove(tx);
+        }
+    }
 }

Added: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionListener.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionListener.java?rev=1668267&view=auto
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionListener.java (added)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionListener.java Sat Mar 21 13:44:55 2015
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * 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.ofbiz.entity.transaction;
+
+import javax.transaction.UserTransaction;
+
+/**
+ * An object that receives notifications from transactions.
+ */
+public interface TransactionListener {
+
+    public enum EventType {
+        BEGIN, COMMIT, ROLLBACK
+    };
+
+    void update(UserTransaction tx, EventType notificationType);
+}

Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionUtil.java?rev=1668267&r1=1668266&r2=1668267&view=diff
==============================================================================
--- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionUtil.java (original)
+++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/transaction/TransactionUtil.java Sat Mar 21 13:44:55 2015
@@ -27,6 +27,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.sql.XAConnection;
 import javax.transaction.HeuristicMixedException;
@@ -78,7 +79,14 @@ public class TransactionUtil implements
     // in order to improve performance allThreadsTransactionBeginStack and allThreadsTransactionBeginStackSave are only maintained when logging level INFO is on
     private static Map<Long, Exception> allThreadsTransactionBeginStack = Collections.<Long, Exception>synchronizedMap(new HashMap<Long, Exception>());
     private static Map<Long, List<Exception>> allThreadsTransactionBeginStackSave = Collections.<Long, List<Exception>>synchronizedMap(new HashMap<Long, List<Exception>>());
+    private static List<TransactionListener> listeners = new CopyOnWriteArrayList<TransactionListener>();
 
+    public static void addListener(TransactionListener listener) {
+        if (listener != null && !listeners.contains(listener)) {
+            listeners.add(listener);
+        }
+    }
+    
     public static <V> V doNewTransaction(Callable<V> callable, String ifErrorMessage, int timeout, boolean printException) throws GenericEntityException {
         return noTransaction(inTransaction(callable, ifErrorMessage, timeout, printException)).call();
     }
@@ -163,7 +171,9 @@ public class TransactionUtil implements
                         Debug.logError(e, module);
                     }
                 }
-
+                for (TransactionListener listener : listeners) {
+                    listener.update(ut, TransactionListener.EventType.BEGIN);
+                }
                 return true;
             } catch (NotSupportedException e) {
                 throw new GenericTransactionException("Not Supported error, could not begin transaction (probably a nesting problem)", e);
@@ -252,7 +262,9 @@ public class TransactionUtil implements
                     // clear out the stack too
                     clearTransactionBeginStack();
                     clearSetRollbackOnlyCause();
-
+                    for (TransactionListener listener : listeners) {
+                        listener.update(ut, TransactionListener.EventType.COMMIT);
+                    }
                     Debug.logVerbose("Transaction committed", module);
                 } else {
                     Debug.logWarning("Not committing transaction, status is " + getStatusString(), module);
@@ -328,7 +340,9 @@ public class TransactionUtil implements
                     // clear out the stack too
                     clearTransactionBeginStack();
                     clearSetRollbackOnlyCause();
-
+                    for (TransactionListener listener : listeners) {
+                        listener.update(ut, TransactionListener.EventType.ROLLBACK);
+                    }
                     ut.rollback();
                     Debug.logInfo("Transaction rolled back", module);
                 } else {