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 {