[hibernate-commits] Hibernate SVN: r17643 - in core/trunk/cache-jbosscache/src: main/java/org/hibernate/cache/jbc/access and 4 other directories.
hibernate-commits at lists.jboss.org
hibernate-commits at lists.jboss.org
Wed Oct 7 19:30:51 EDT 2009
Author: bstansberry at jboss.com
Date: 2009-10-07 19:30:50 -0400 (Wed, 07 Oct 2009)
New Revision: 17643
Added:
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java
core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/
core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/PutFromLoadValidatorUnitTestCase.java
Modified:
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/BasicRegionAdapter.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/OptimisticTransactionalAccessDelegate.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/TransactionalAccessDelegate.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/CollectionRegionImpl.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/OptimisticTransactionalAccess.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/TransactionalAccess.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/EntityRegionImpl.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/OptimisticTransactionalAccess.java
core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/TransactionalAccess.java
Log:
[HHH-3817] Create PutFromLoadValidator class to control whether puts are allowed after removes/evicts
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/BasicRegionAdapter.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/BasicRegionAdapter.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/BasicRegionAdapter.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -445,20 +445,6 @@
resume(tx);
}
}
-
- public Object getOwnerForPut()
- {
- Transaction tx = null;
- try {
- if (transactionManager != null) {
- tx = transactionManager.getTransaction();
- }
- } catch (SystemException se) {
- throw new CacheException("Could not obtain transaction", se);
- }
- return tx == null ? Thread.currentThread() : tx;
-
- }
/**
* Tell the TransactionManager to suspend any ongoing transaction.
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/OptimisticTransactionalAccessDelegate.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/OptimisticTransactionalAccessDelegate.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/OptimisticTransactionalAccessDelegate.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -34,7 +34,6 @@
import org.hibernate.cache.jbc.util.DataVersionAdapter;
import org.hibernate.cache.jbc.util.NonLockingDataVersion;
import org.jboss.cache.config.Option;
-import org.jboss.cache.optimistic.DataVersion;
/**
* Defines the strategy for transactional access to entity or collection data in
@@ -52,8 +51,8 @@
protected final CacheDataDescription dataDescription;
- public OptimisticTransactionalAccessDelegate(TransactionalDataRegionAdapter region) {
- super(region);
+ public OptimisticTransactionalAccessDelegate(TransactionalDataRegionAdapter region, PutFromLoadValidator validator) {
+ super(region, validator);
this.dataDescription = region.getCacheDataDescription();
}
@@ -64,7 +63,7 @@
*/
@Override
public void evict(Object key) throws CacheException {
- pendingPuts.remove(key);
+ putValidator.keyRemoved(key);
region.ensureRegionRootExists();
Option opt = NonLockingDataVersion.getInvocationOption();
@@ -76,7 +75,8 @@
@Override
public void evictAll() throws CacheException
{
- pendingPuts.clear();
+ putValidator.regionRemoved();
+
Transaction tx = region.suspend();
try {
region.ensureRegionRootExists();
@@ -96,8 +96,6 @@
@Override
public boolean insert(Object key, Object value, Object version) throws CacheException {
- pendingPuts.remove(key);
-
if (!region.checkValid())
return false;
@@ -115,7 +113,7 @@
if (!region.checkValid())
return false;
- if (!isPutValid(key))
+ if (!putValidator.isPutValid(key))
return false;
region.ensureRegionRootExists();
@@ -133,7 +131,7 @@
if (!region.checkValid())
return false;
- if (!isPutValid(key))
+ if (!putValidator.isPutValid(key))
return false;
region.ensureRegionRootExists();
@@ -145,7 +143,7 @@
@Override
public void remove(Object key) throws CacheException {
- pendingPuts.remove(key);
+ putValidator.keyRemoved(key);
// We remove whether or not the region is valid. Other nodes
// may have already restored the region so they need to
@@ -159,7 +157,7 @@
@Override
public void removeAll() throws CacheException {
- pendingPuts.clear();
+ putValidator.regionRemoved();
Option opt = NonLockingDataVersion.getInvocationOption();
CacheHelper.removeAll(cache, regionFqn, opt);
}
@@ -168,8 +166,6 @@
public boolean update(Object key, Object value, Object currentVersion, Object previousVersion)
throws CacheException {
- pendingPuts.remove(key);
-
// We update whether or not the region is valid. Other nodes
// may have already restored the region so they need to
// be informed of the change.
@@ -181,9 +177,10 @@
return true;
}
+ @SuppressWarnings("deprecation")
private Option getDataVersionOption(Object currentVersion, Object previousVersion) {
- DataVersion dv = (dataDescription != null && dataDescription.isVersioned()) ? new DataVersionAdapter(
+ org.jboss.cache.optimistic.DataVersion dv = (dataDescription != null && dataDescription.isVersioned()) ? new DataVersionAdapter(
currentVersion, previousVersion, dataDescription.getVersionComparator(), dataDescription.toString())
: NonLockingDataVersion.INSTANCE;
Option opt = new Option();
Added: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java (rev 0)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -0,0 +1,453 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2009, Red Hat, Inc or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors. All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301 USA
+ */
+package org.hibernate.cache.jbc.access;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.hibernate.cache.CacheException;
+
+
+/**
+ * Encapsulates logic to allow a {@link TransactionalAccessDelegate} to determine
+ * whether a {@link TransactionalAccessDelegate#putFromLoad(Object, Object, long, Object, boolean)
+ * call should be allowed to update the cache. A <code>putFromLoad</code> has
+ * the potential to store stale data, since the data may have been removed from the
+ * database and the cache between the time when the data was read from the database
+ * and the actual call to <code>putFromLoad</code>.
+ *
+ * @author Brian Stansberry
+ *
+ * @version $Revision: $
+ */
+public class PutFromLoadValidator
+{
+ /**
+ * Period in ms after a removal during which a call to {@link #isPutValid(Object)}
+ * that hasn't been {@link #registerPendingPut(Object) pre-registered}
+ * (aka a "naked put") will return false.
+ */
+ public static final long NAKED_PUT_INVALIDATION_PERIOD = 10 * 1000;
+
+ /** Period after which a pending put is placed in the over-age queue */
+ private static final long PENDING_PUT_OVERAGE_PERIOD = 5 * 1000;
+
+ /** Period before which we stop trying to clean out pending puts */
+ private static final long PENDING_PUT_RECENT_PERIOD = 2 * 1000;
+
+ /** Period after which a pending put is never expected to come in
+ * and should be cleaned */
+ private static final long MAX_PENDING_PUT_DELAY = 2 * 60 * 1000;
+
+ /** Used to determine whether the owner of a pending put is a thread or a transaction */
+ private final TransactionManager transactionManager;
+
+ private final long nakedPutInvalidationPeriod;
+ private final long pendingPutOveragePeriod;
+ private final long pendingPutRecentPeriod;
+ private final long maxPendingPutDelay;
+
+ /**
+ * Registry of expected, future, isPutValid calls. If a key+owner is registered
+ * in this map, it is not a "naked put" and is allowed to proceed.
+ */
+ private final ConcurrentMap<Object, PendingPutMap> pendingPuts =
+ new ConcurrentHashMap<Object, PendingPutMap>();
+ /** List of pending puts. Used to ensure we don't leak memory via the pendingPuts map */
+ private final List<WeakReference<PendingPut>> pendingQueue = new LinkedList<WeakReference<PendingPut>>();
+ /** Separate list of pending puts that haven't been resolved within PENDING_PUT_OVERAGE_PERIOD.
+ * Used to ensure we don't leak memory via the pendingPuts map.
+ * Tracked separately from more recent pending puts for efficiency reasons. */
+ private final List<WeakReference<PendingPut>> overagePendingQueue = new LinkedList<WeakReference<PendingPut>>();
+ /** Lock controlling access to pending put queues */
+ private final Lock pendingLock = new ReentrantLock();
+ private final ConcurrentMap<Object, Long> recentRemovals = new ConcurrentHashMap<Object, Long>();
+ /** List of recent removals. Used to ensure we don't leak memory via the recentRemovals map */
+ private final List<RecentRemoval> removalsQueue = new LinkedList<RecentRemoval>();
+ /** The time when the first element in removalsQueue will expire. No reason to do
+ * housekeeping on the queue before this time. */
+ private volatile long earliestRemovalTimestamp;
+ /** Lock controlling access to removalsQueue */
+ private final Lock removalsLock = new ReentrantLock();
+
+ /**
+ * The time of the last call to regionRemoved(), plus NAKED_PUT_INVALIDATION_PERIOD.
+ * All naked puts will be rejected until the current time is greater than this value.
+ */
+ private volatile long invalidationTimestamp;
+
+ /**
+ * Creates a new PutFromLoadValidator.
+ *
+ * @param transactionManager transaction manager to use to associated changes with a transaction;
+ * may be <code>null</code>
+ */
+ public PutFromLoadValidator(TransactionManager transactionManager) {
+ this(transactionManager, NAKED_PUT_INVALIDATION_PERIOD, PENDING_PUT_OVERAGE_PERIOD, PENDING_PUT_RECENT_PERIOD, MAX_PENDING_PUT_DELAY);
+ }
+
+ /** Constructor variant for use by unit tests; allows control of variouts timeouts by the test. */
+ protected PutFromLoadValidator(TransactionManager transactionManager, long nakedPutInvalidationPeriod, long pendingPutOveragePeriod, long pendingPutRecentPeriod, long maxPendingPutDelay) {
+ this.transactionManager = transactionManager;
+ this.nakedPutInvalidationPeriod = nakedPutInvalidationPeriod;
+ this.pendingPutOveragePeriod = pendingPutOveragePeriod;
+ this.pendingPutRecentPeriod = pendingPutRecentPeriod;
+ this.maxPendingPutDelay = maxPendingPutDelay;
+ }
+
+ public boolean isPutValid(Object key)
+ {
+ boolean valid = false;
+ long now = System.currentTimeMillis();
+
+ PendingPutMap pending = pendingPuts.get(key);
+ if (pending != null) {
+ synchronized (pending) {
+ PendingPut toCancel = pending.remove(getOwnerForPut());
+ valid = toCancel != null;
+ if (valid) {
+ toCancel.completed = true;
+ if (pending.size() == 0) {
+ pendingPuts.remove(key);
+ }
+ }
+ }
+ }
+
+ if (!valid) {
+ if (now > invalidationTimestamp) {
+ Long removedTime = recentRemovals.get(key);
+ if (removedTime == null || now > removedTime.longValue()) {
+ valid = true;
+ }
+ }
+ }
+
+ cleanOutdatedPendingPuts(now, true);
+
+ return valid;
+ }
+
+ public void keyRemoved(Object key)
+ {
+ // Invalidate any pending puts
+ pendingPuts.remove(key);
+
+ // Record when this occurred to invalidate later naked puts
+ RecentRemoval removal = new RecentRemoval(key, this.nakedPutInvalidationPeriod);
+ recentRemovals.put(key, removal.timestamp);
+
+ // Don't let recentRemovals map become a memory leak
+ RecentRemoval toClean = null;
+ boolean attemptClean = removal.timestamp.longValue() > earliestRemovalTimestamp;
+ removalsLock.lock();
+ try {
+ removalsQueue.add(removal);
+
+ if (attemptClean) {
+ if (removalsQueue.size() > 1) { // we have at least one as we just added it
+ toClean = removalsQueue.remove(0);
+ }
+ earliestRemovalTimestamp = removalsQueue.get(0).timestamp.longValue();
+ }
+ }
+ finally {
+ removalsLock.unlock();
+ }
+
+ if (toClean != null) {
+ Long cleaned = recentRemovals.get(toClean.key);
+ if (cleaned != null && cleaned.equals(toClean.timestamp)) {
+ cleaned = recentRemovals.remove(toClean.key);
+ if (cleaned != null && cleaned.equals(toClean.timestamp) == false) {
+ // Oops; removed the wrong timestamp; restore it
+ recentRemovals.putIfAbsent(toClean.key, cleaned);
+ }
+ }
+ }
+ }
+
+ public void regionRemoved()
+ {
+ invalidationTimestamp = System.currentTimeMillis() + this.nakedPutInvalidationPeriod;
+ pendingLock.lock();
+ try {
+ removalsLock.lock();
+ try {
+ pendingPuts.clear();
+ pendingQueue.clear();
+ overagePendingQueue.clear();
+ recentRemovals.clear();
+ removalsQueue.clear();
+ earliestRemovalTimestamp = invalidationTimestamp;
+
+ }
+ finally {
+ removalsLock.unlock();
+ }
+ }
+ finally {
+ pendingLock.unlock();
+ }
+ }
+
+ /**
+ * Notifies this validator that it is expected that a database read followed by a
+ * subsequent {@link #isPutValid(Object)} call will occur. The intent is this method
+ * would be called following a cache miss wherein it is expected that a database
+ * read plus cache put will occur. Calling this method allows the validator to treat
+ * the subsequent <code>isPutValid</code> as if the database read occurred when
+ * this method was invoked. This allows the validator to compare the timestamp of
+ * this call against the timestamp of subsequent removal notifications. A put that
+ * occurs without this call preceding it is "naked"; i.e the validator must assume
+ * the put is not valid if any relevant removal has occurred within
+ * {@link #NAKED_PUT_INVALIDATION_PERIOD} milliseconds.
+ *
+ * @param key key that will be used for subsequent put
+ */
+ public void registerPendingPut(Object key)
+ {
+ PendingPut pendingPut = new PendingPut(key, getOwnerForPut());
+ PendingPutMap pendingForKey = new PendingPutMap();
+ synchronized (pendingForKey) {
+ for (;;) {
+ PendingPutMap existing = pendingPuts.putIfAbsent(key, pendingForKey);
+ if (existing != null && existing != pendingForKey) {
+ synchronized (existing) {
+ existing.put(pendingPut);
+ PendingPutMap doublecheck = pendingPuts.putIfAbsent(key, existing);
+ if (doublecheck == null || doublecheck == existing) {
+ break;
+ }
+ // else we hit a race and need to loop to try again
+ }
+ }
+ else {
+ pendingForKey.put(pendingPut);
+ break;
+ }
+ }
+ }
+
+ // Guard against memory leaks
+ preventOutdatedPendingPuts(pendingPut);
+ }
+
+ private Object getOwnerForPut()
+ {
+ Transaction tx = null;
+ try {
+ if (transactionManager != null) {
+ tx = transactionManager.getTransaction();
+ }
+ } catch (SystemException se) {
+ throw new CacheException("Could not obtain transaction", se);
+ }
+ return tx == null ? Thread.currentThread() : tx;
+
+ }
+
+ private void preventOutdatedPendingPuts(PendingPut pendingPut)
+ {
+ pendingLock.lock();
+ try {
+ pendingQueue.add(new WeakReference<PendingPut>(pendingPut));
+ cleanOutdatedPendingPuts(pendingPut.timestamp, false);
+ }
+ finally {
+ pendingLock.unlock();
+ }
+ }
+
+ private void cleanOutdatedPendingPuts(long now, boolean lock)
+ {
+
+ PendingPut toClean = null;
+ if (lock) {
+ pendingLock.lock();
+ }
+ try {
+
+ // Clean items out of the basic queue
+
+ long overaged = now - this.pendingPutOveragePeriod;
+ long recent = now - this.pendingPutRecentPeriod;
+
+ int pos = 0;
+ while (pendingQueue.size() > pos) {
+ WeakReference<PendingPut> ref = pendingQueue.get(0);
+ PendingPut item = ref.get();
+ if (item == null || item.completed) {
+ pendingQueue.remove(pos);
+ }
+ else if (item.timestamp < overaged) {
+ // Potential leak; move to the overaged queued
+ pendingQueue.remove(pos);
+ overagePendingQueue.add(ref);
+ }
+ else if (item.timestamp >= recent) {
+ // Don't waste time on very recent items
+ break;
+ }
+ else if (pos > 2) {
+ // Don't spend too much time getting nowhere
+ break;
+ }
+ else {
+ // Move on to the next item
+ pos++;
+ }
+ }
+
+ // Process the overage queue until we find an item to clean
+ // or an incomplete item that hasn't aged out
+ long mustCleanTime = now - this.maxPendingPutDelay;
+
+ while (overagePendingQueue.size() > 0) {
+ WeakReference<PendingPut> ref = overagePendingQueue.get(0);
+ PendingPut item = ref.get();
+ if (item == null || item.completed) {
+ overagePendingQueue.remove(0);
+ }
+ else {
+ if (item.timestamp < mustCleanTime) {
+ toClean = item;
+ }
+ break;
+ }
+ }
+ }
+ finally {
+ if (lock) {
+ pendingLock.unlock();
+ }
+ }
+
+ // We've found a pendingPut that never happened; clean it up
+ if (toClean != null) {
+ PendingPutMap map = pendingPuts.get(toClean.key);
+ if (map != null) {
+ synchronized (map) {
+ PendingPut cleaned = map.remove(toClean.owner);
+ if (toClean.equals(cleaned) == false) {
+ // Oops. Restore it.
+ map.put(cleaned);
+ }
+ else if (map.size() == 0) {
+ pendingPuts.remove(toClean.key);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Lazy-initialization map for PendingPut. Optimized
+ * for the expected usual case where only a single put
+ * is pending for a given key.
+ *
+ * This class is NOT THREAD SAFE. All operations on it
+ * must be performed with the object monitor held.
+ */
+ private static class PendingPutMap {
+ private PendingPut singlePendingPut;
+ private Map<Object, PendingPut> fullMap;
+
+ public void put(PendingPut pendingPut)
+ {
+ if (singlePendingPut == null) {
+ if (fullMap == null) {
+ // initial put
+ singlePendingPut = pendingPut;
+ }
+ else {
+ fullMap.put(pendingPut.owner, pendingPut);
+ }
+ }
+ else {
+ // 2nd put; need a map
+ fullMap = new HashMap<Object, PendingPut>(4);
+ fullMap.put(singlePendingPut.owner, singlePendingPut);
+ singlePendingPut = null;
+ fullMap.put(pendingPut.owner, pendingPut);
+ }
+ }
+
+ public PendingPut remove(Object ownerForPut)
+ {
+ PendingPut removed = null;
+ if (fullMap == null) {
+ if (singlePendingPut != null && singlePendingPut.owner.equals(ownerForPut)) {
+ removed = singlePendingPut;
+ singlePendingPut = null;
+ }
+ }
+ else {
+ removed = fullMap.remove(ownerForPut);
+ }
+ return removed;
+ }
+
+ public int size()
+ {
+ return fullMap == null ? (singlePendingPut == null ? 0 : 1) : fullMap.size();
+ }
+ }
+
+ private static class PendingPut {
+ private final Object key;
+ private final Object owner;
+ private final long timestamp = System.currentTimeMillis();
+ private volatile boolean completed;
+
+ private PendingPut(Object key, Object owner) {
+ this.key = key;
+ this.owner = owner;
+ }
+
+ }
+
+ private static class RecentRemoval {
+ private final Object key;
+ private final Long timestamp;
+
+ private RecentRemoval(Object key, long nakedPutInvalidationPeriod) {
+ this.key = key;
+ timestamp = Long.valueOf(System.currentTimeMillis() + nakedPutInvalidationPeriod);
+ }
+ }
+
+}
Property changes on: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java
___________________________________________________________________
Name: svn:keywords
+
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/TransactionalAccessDelegate.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/TransactionalAccessDelegate.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/TransactionalAccessDelegate.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -23,11 +23,6 @@
*/
package org.hibernate.cache.jbc.access;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
import javax.transaction.Transaction;
import org.hibernate.cache.CacheException;
@@ -55,13 +50,13 @@
protected final Cache cache;
protected final Fqn regionFqn;
protected final BasicRegionAdapter region;
- protected final ConcurrentMap<Object, Set<Object>> pendingPuts =
- new ConcurrentHashMap<Object, Set<Object>>();
+ protected final PutFromLoadValidator putValidator;
- public TransactionalAccessDelegate(BasicRegionAdapter adapter) {
+ public TransactionalAccessDelegate(BasicRegionAdapter adapter, PutFromLoadValidator validator) {
this.region = adapter;
this.cache = adapter.getCacheInstance();
this.regionFqn = adapter.getRegionFqn();
+ this.putValidator = validator;
}
public Object get(Object key, long txTimestamp) throws CacheException {
@@ -74,7 +69,7 @@
Object val = CacheHelper.get(cache, regionFqn, key);
if (val == null) {
- registerPendingPut(key);
+ putValidator.registerPendingPut(key);
}
return val;
@@ -85,7 +80,7 @@
if (!region.checkValid())
return false;
- if (!isPutValid(key))
+ if (!putValidator.isPutValid(key))
return false;
region.ensureRegionRootExists();
@@ -99,7 +94,7 @@
if (!region.checkValid())
return false;
- if (!isPutValid(key))
+ if (!putValidator.isPutValid(key))
return false;
region.ensureRegionRootExists();
@@ -126,8 +121,6 @@
public boolean insert(Object key, Object value, Object version) throws CacheException {
- pendingPuts.remove(key);
-
if (!region.checkValid())
return false;
@@ -144,8 +137,6 @@
public boolean update(Object key, Object value, Object currentVersion, Object previousVersion)
throws CacheException {
- pendingPuts.remove(key);
-
// We update whether or not the region is valid. Other nodes
// may have already restored the region so they need to
// be informed of the change.
@@ -163,7 +154,7 @@
public void remove(Object key) throws CacheException {
- pendingPuts.remove(key);
+ putValidator.keyRemoved(key);
// We remove whether or not the region is valid. Other nodes
// may have already restored the region so they need to
@@ -175,13 +166,13 @@
}
public void removeAll() throws CacheException {
- pendingPuts.clear();
+ putValidator.regionRemoved();
CacheHelper.removeAll(cache, regionFqn);
}
public void evict(Object key) throws CacheException {
- pendingPuts.remove(key);
+ putValidator.keyRemoved(key);
region.ensureRegionRootExists();
@@ -189,7 +180,7 @@
}
public void evictAll() throws CacheException {
- pendingPuts.clear();
+ putValidator.regionRemoved();
Transaction tx = region.suspend();
try {
region.ensureRegionRootExists();
@@ -200,37 +191,4 @@
region.resume(tx);
}
}
-
- protected void registerPendingPut(Object key)
- {
- Set<Object> pending = pendingPuts.get(key);
- if (pending == null) {
- pending = new HashSet<Object>();
- }
-
- synchronized (pending) {
- Object owner = region.getOwnerForPut();
- pending.add(owner);
- Set<Object> existing = pendingPuts.putIfAbsent(key, pending);
- if (existing != pending) {
- // try again
- registerPendingPut(key);
- }
- }
- }
-
- protected boolean isPutValid(Object key)
- {
- boolean valid = false;
- Set<Object> pending = pendingPuts.get(key);
- if (pending != null) {
- synchronized (pending) {
- valid = pending.remove(region.getOwnerForPut());
- if (valid && pending.size() == 0) {
- pendingPuts.remove(key);
- }
- }
- }
- return valid;
- }
}
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/CollectionRegionImpl.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/CollectionRegionImpl.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/CollectionRegionImpl.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -34,6 +34,7 @@
import org.hibernate.cache.access.AccessType;
import org.hibernate.cache.access.CollectionRegionAccessStrategy;
import org.hibernate.cache.jbc.TransactionalDataRegionAdapter;
+import org.hibernate.cache.jbc.access.PutFromLoadValidator;
/**
* Defines the behavior of the collection cache regions for JBossCache 2.x.
@@ -68,4 +69,8 @@
protected Fqn<String> createRegionFqn(String regionName, String regionPrefix) {
return getTypeLastRegionFqn(regionName, regionPrefix, TYPE);
}
+
+ public PutFromLoadValidator getPutFromLoadValidator() {
+ return new PutFromLoadValidator(transactionManager);
+ }
}
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/OptimisticTransactionalAccess.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/OptimisticTransactionalAccess.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/OptimisticTransactionalAccess.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -42,7 +42,7 @@
public OptimisticTransactionalAccess(CollectionRegionImpl region) {
// We use a different delegate than the non-optimistic superclass default
- super(region, new OptimisticTransactionalAccessDelegate(region));
+ super(region, new OptimisticTransactionalAccessDelegate(region, region.getPutFromLoadValidator()));
}
}
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/TransactionalAccess.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/TransactionalAccess.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/collection/TransactionalAccess.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -52,7 +52,7 @@
* @param region the region to which this provides access
*/
public TransactionalAccess(CollectionRegionImpl region) {
- this(region, new TransactionalAccessDelegate(region));
+ this(region, new TransactionalAccessDelegate(region, region.getPutFromLoadValidator()));
}
/**
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/EntityRegionImpl.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/EntityRegionImpl.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/EntityRegionImpl.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -34,6 +34,7 @@
import org.hibernate.cache.access.AccessType;
import org.hibernate.cache.access.EntityRegionAccessStrategy;
import org.hibernate.cache.jbc.TransactionalDataRegionAdapter;
+import org.hibernate.cache.jbc.access.PutFromLoadValidator;
/**
* Defines the behavior of the entity cache regions for JBossCache.
@@ -72,5 +73,9 @@
protected Fqn<String> createRegionFqn(String regionName, String regionPrefix) {
return getTypeLastRegionFqn(regionName, regionPrefix, TYPE);
}
+
+ public PutFromLoadValidator getPutFromLoadValidator() {
+ return new PutFromLoadValidator(transactionManager);
+ }
}
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/OptimisticTransactionalAccess.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/OptimisticTransactionalAccess.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/OptimisticTransactionalAccess.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -41,6 +41,6 @@
* @param region The region\ to which this is providing access
*/
public OptimisticTransactionalAccess(EntityRegionImpl region) {
- super(region, new OptimisticTransactionalAccessDelegate(region));
+ super(region, new OptimisticTransactionalAccessDelegate(region, region.getPutFromLoadValidator()));
}
}
Modified: core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/TransactionalAccess.java
===================================================================
--- core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/TransactionalAccess.java 2009-10-07 20:45:06 UTC (rev 17642)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/entity/TransactionalAccess.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -46,7 +46,7 @@
private final TransactionalAccessDelegate delegate;
public TransactionalAccess(EntityRegionImpl region) {
- this(region, new TransactionalAccessDelegate(region));
+ this(region, new TransactionalAccessDelegate(region, region.getPutFromLoadValidator()));
}
protected TransactionalAccess(EntityRegionImpl region, TransactionalAccessDelegate delegate) {
Added: core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/PutFromLoadValidatorUnitTestCase.java
===================================================================
--- core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/PutFromLoadValidatorUnitTestCase.java (rev 0)
+++ core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/PutFromLoadValidatorUnitTestCase.java 2009-10-07 23:30:50 UTC (rev 17643)
@@ -0,0 +1,246 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2009, Red Hat, Inc or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors. All third-party contributions are
+ * distributed under license by Red Hat Middleware LLC.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02110-1301 USA
+ */
+package org.hibernate.test.cache.jbc.access;
+
+import javax.transaction.TransactionManager;
+
+import org.hibernate.cache.jbc.access.PutFromLoadValidator;
+import org.hibernate.test.cache.jbc.functional.util.DualNodeJtaTransactionManagerImpl;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests of {@link PutFromLoadValidator}.
+ *
+ * @author Brian Stansberry
+ *
+ * @version $Revision: $
+ */
+public class PutFromLoadValidatorUnitTestCase extends TestCase
+{
+ private Object KEY1= "KEY1";
+
+ private TransactionManager tm;
+
+ public PutFromLoadValidatorUnitTestCase(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ tm = DualNodeJtaTransactionManagerImpl.getInstance("test");
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ try {
+ super.tearDown();
+ }
+ finally {
+ tm = null;
+ try {
+ DualNodeJtaTransactionManagerImpl.cleanupTransactions();
+ }
+ finally {
+ DualNodeJtaTransactionManagerImpl.cleanupTransactionManagers();
+ }
+ }
+ }
+
+ public void testNakedPut() throws Exception {
+ nakedPutTest(false);
+ }
+
+ public void testNakedPutTransactional() throws Exception {
+ nakedPutTest(true);
+ }
+
+ private void nakedPutTest(boolean transactional) throws Exception {
+ PutFromLoadValidator testee = new PutFromLoadValidator(transactional ? tm : null);
+ if (transactional) {
+ tm.begin();
+ }
+ assertTrue(testee.isPutValid(KEY1));
+ }
+
+ public void testRegisteredPut() throws Exception {
+ registeredPutTest(false);
+ }
+
+ public void testRegisteredPutTransactional() throws Exception {
+ registeredPutTest(true);
+ }
+
+ private void registeredPutTest(boolean transactional) throws Exception {
+ PutFromLoadValidator testee = new PutFromLoadValidator(transactional ? tm : null);
+ if (transactional) {
+ tm.begin();
+ }
+ testee.registerPendingPut(KEY1);
+ assertTrue(testee.isPutValid(KEY1));
+ }
+
+ public void testNakedPutAfterKeyRemoval() throws Exception {
+ nakedPutAfterRemovalTest(false, false);
+ }
+
+ public void testNakedPutAfterKeyRemovalTransactional() throws Exception {
+ nakedPutAfterRemovalTest(true, false);
+ }
+
+ public void testNakedPutAfterRegionRemoval() throws Exception {
+ nakedPutAfterRemovalTest(false, true);
+ }
+
+ public void testNakedPutAfterRegionRemovalTransactional() throws Exception {
+ nakedPutAfterRemovalTest(true, true);
+ }
+
+ private void nakedPutAfterRemovalTest(boolean transactional, boolean removeRegion) throws Exception
+ {
+ PutFromLoadValidator testee = new PutFromLoadValidator(transactional ? tm : null);
+ if (removeRegion) {
+ testee.regionRemoved();
+ }
+ else {
+ testee.keyRemoved(KEY1);
+ }
+ if (transactional) {
+ tm.begin();
+ }
+ assertFalse(testee.isPutValid(KEY1));
+
+ }
+
+ public void testRegisteredPutAfterKeyRemoval() throws Exception {
+ registeredPutAfterRemovalTest(false, false);
+ }
+
+ public void testRegisteredPutAfterKeyRemovalTransactional() throws Exception {
+ registeredPutAfterRemovalTest(true, false);
+ }
+
+ public void testRegisteredPutAfterRegionRemoval() throws Exception {
+ registeredPutAfterRemovalTest(false, true);
+ }
+
+ public void testRegisteredPutAfterRegionRemovalTransactional() throws Exception {
+ registeredPutAfterRemovalTest(true, true);
+ }
+
+ private void registeredPutAfterRemovalTest(boolean transactional, boolean removeRegion) throws Exception
+ {
+ PutFromLoadValidator testee = new PutFromLoadValidator(transactional ? tm : null);
+ if (removeRegion) {
+ testee.regionRemoved();
+ }
+ else {
+ testee.keyRemoved(KEY1);
+ }
+ if (transactional) {
+ tm.begin();
+ }
+ testee.registerPendingPut(KEY1);
+ assertTrue(testee.isPutValid(KEY1));
+ }
+
+ public void testRegisteredPutWithInterveningKeyRemoval() throws Exception {
+ registeredPutWithInterveningRemovalTest(false, false);
+ }
+
+ public void testRegisteredPutWithInterveningKeyRemovalTransactional() throws Exception {
+ registeredPutWithInterveningRemovalTest(true, false);
+ }
+
+ public void testRegisteredPutWithInterveningRegionRemoval() throws Exception {
+ registeredPutWithInterveningRemovalTest(false, true);
+ }
+
+ public void testRegisteredPutWithInterveningRegionRemovalTransactional() throws Exception {
+ registeredPutWithInterveningRemovalTest(true, true);
+ }
+
+ private void registeredPutWithInterveningRemovalTest(boolean transactional, boolean removeRegion) throws Exception
+ {
+ PutFromLoadValidator testee = new PutFromLoadValidator(transactional ? tm : null);
+ if (transactional) {
+ tm.begin();
+ }
+ testee.registerPendingPut(KEY1);
+ if (removeRegion) {
+ testee.regionRemoved();
+ }
+ else {
+ testee.keyRemoved(KEY1);
+ }
+ assertFalse(testee.isPutValid(KEY1));
+ }
+
+ public void testDelayedNakedPutAfterKeyRemoval() throws Exception {
+ delayedNakedPutAfterRemovalTest(false, false);
+ }
+
+ public void testDelayedNakedPutAfterKeyRemovalTransactional() throws Exception {
+ delayedNakedPutAfterRemovalTest(true, false);
+ }
+
+ public void testDelayedNakedPutAfterRegionRemoval() throws Exception {
+ delayedNakedPutAfterRemovalTest(false, true);
+ }
+
+ public void testDelayedNakedPutAfterRegionRemovalTransactional() throws Exception {
+ delayedNakedPutAfterRemovalTest(true, true);
+ }
+
+ private void delayedNakedPutAfterRemovalTest(boolean transactional, boolean removeRegion) throws Exception
+ {
+ PutFromLoadValidator testee = new TestValidator(transactional ? tm : null, 100, 1000, 500, 10000);
+ if (removeRegion) {
+ testee.regionRemoved();
+ }
+ else {
+ testee.keyRemoved(KEY1);
+ }
+ if (transactional) {
+ tm.begin();
+ }
+ Thread.sleep(110);
+ assertTrue(testee.isPutValid(KEY1));
+
+ }
+
+ private static class TestValidator extends PutFromLoadValidator {
+
+ protected TestValidator(TransactionManager transactionManager, long nakedPutInvalidationPeriod,
+ long pendingPutOveragePeriod, long pendingPutRecentPeriod, long maxPendingPutDelay)
+ {
+ super(transactionManager, nakedPutInvalidationPeriod, pendingPutOveragePeriod, pendingPutRecentPeriod,
+ maxPendingPutDelay);
+ }
+
+ }
+}
Property changes on: core/trunk/cache-jbosscache/src/test/java/org/hibernate/test/cache/jbc/access/PutFromLoadValidatorUnitTestCase.java
___________________________________________________________________
Name: svn:keywords
+
More information about the hibernate-commits
mailing list