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 { |
Free forum by Nabble | Edit this page |