HHH-9868 Infinispan 2LC can store stale data
* invalidation blocks putFromLoads until the transaction with invalidation is committed * rewritten the naked puts support: timestamp is stored in the pendingPutMap and removal of the record relies on pending puts' idle expiration or piggy-backs on release from putFromLoad
This commit is contained in:
parent
fa8e94071f
commit
4b2a78785e
|
@ -10,13 +10,13 @@ import javax.transaction.SystemException;
|
||||||
import javax.transaction.Transaction;
|
import javax.transaction.Transaction;
|
||||||
import javax.transaction.TransactionManager;
|
import javax.transaction.TransactionManager;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import org.hibernate.cache.CacheException;
|
import org.hibernate.cache.CacheException;
|
||||||
|
@ -41,9 +41,9 @@ import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
* <li> Call {@link #registerPendingPut(Object)}</li>
|
* <li> Call {@link #registerPendingPut(Object)}</li>
|
||||||
* <li> Read the database</li>
|
* <li> Read the database</li>
|
||||||
* <li> Call {@link #acquirePutFromLoadLock(Object)}
|
* <li> Call {@link #acquirePutFromLoadLock(Object)}
|
||||||
* <li> if above returns <code>false</code>, the thread should not cache the data;
|
* <li> if above returns <code>null</code>, the thread should not cache the data;
|
||||||
* only if above returns <code>true</code>, put data in the cache and...</li>
|
* only if above returns instance of <code>AcquiredLock</code>, put data in the cache and...</li>
|
||||||
* <li> then call {@link #releasePutFromLoadLock(Object)}</li>
|
* <li> then call {@link #releasePutFromLoadLock(Object, Lock)}</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* </p>
|
* </p>
|
||||||
* <p/>
|
* <p/>
|
||||||
|
@ -53,15 +53,19 @@ import org.infinispan.manager.EmbeddedCacheManager;
|
||||||
* call
|
* call
|
||||||
* <p/>
|
* <p/>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li> {@link #invalidateKey(Object)} (for a single key invalidation)</li>
|
* <li> {@link #beginInvalidatingKey(Object)} (for a single key invalidation)</li>
|
||||||
* <li>or {@link #invalidateRegion()} (for a general invalidation all pending puts)</li>
|
* <li>or {@link #invalidateRegion()} (for a general invalidation all pending puts)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* After transaction commit (when the DB is updated) {@link #endInvalidatingKey(Object)} should
|
||||||
|
* be called in order to allow further attempts to cache entry.
|
||||||
* </p>
|
* </p>
|
||||||
* <p/>
|
* <p/>
|
||||||
* <p>
|
* <p>
|
||||||
* This class also supports the concept of "naked puts", which are calls to
|
* This class also supports the concept of "naked puts", which are calls to
|
||||||
* {@link #acquirePutFromLoadLock(Object)} without a preceding {@link #registerPendingPut(Object)}
|
* {@link #acquirePutFromLoadLock(Object)} without a preceding {@link #registerPendingPut(Object)}.
|
||||||
* call.
|
* Besides not acquiring lock in {@link #registerPendingPut(Object)} this can happen when collection
|
||||||
|
* elements are loaded after the collection has not been found in the cache, where the elements
|
||||||
|
* don't have their own table but can be listed as 'select ... from Element where collection_id = ...'.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Brian Stansberry
|
* @author Brian Stansberry
|
||||||
|
@ -89,26 +93,15 @@ public class PutFromLoadValidator {
|
||||||
*/
|
*/
|
||||||
private final ConcurrentMap<Object, PendingPutMap> pendingPuts;
|
private final ConcurrentMap<Object, PendingPutMap> pendingPuts;
|
||||||
|
|
||||||
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
|
* The time of the last call to {@link #invalidateRegion()}, plus NAKED_PUT_INVALIDATION_PERIOD. All naked
|
||||||
*/
|
|
||||||
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.
|
* puts will be rejected until the current time is greater than this value.
|
||||||
|
* NOTE: update only through {@link #invalidationUpdater}!
|
||||||
*/
|
*/
|
||||||
private volatile long invalidationTimestamp;
|
private volatile long invalidationTimestamp = Long.MIN_VALUE;
|
||||||
|
|
||||||
|
private static final AtomicLongFieldUpdater<PutFromLoadValidator> invalidationUpdater
|
||||||
|
= AtomicLongFieldUpdater.newUpdater(PutFromLoadValidator.class, "invalidationTimestamp");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new put from load validator instance.
|
* Creates a new put from load validator instance.
|
||||||
|
@ -133,9 +126,7 @@ public class PutFromLoadValidator {
|
||||||
public PutFromLoadValidator(
|
public PutFromLoadValidator(
|
||||||
AdvancedCache cache, TransactionManager transactionManager,
|
AdvancedCache cache, TransactionManager transactionManager,
|
||||||
long nakedPutInvalidationPeriod) {
|
long nakedPutInvalidationPeriod) {
|
||||||
this(cache, cache.getCacheManager(), transactionManager,
|
this(cache, cache.getCacheManager(), transactionManager, nakedPutInvalidationPeriod);
|
||||||
nakedPutInvalidationPeriod
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,81 +161,100 @@ public class PutFromLoadValidator {
|
||||||
|
|
||||||
// ----------------------------------------------------------------- Public
|
// ----------------------------------------------------------------- Public
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker for lock acquired in {@link #acquirePutFromLoadLock(Object)}
|
||||||
|
*/
|
||||||
|
public static class Lock {
|
||||||
|
protected Lock() {}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acquire a lock giving the calling thread the right to put data in the
|
* Acquire a lock giving the calling thread the right to put data in the
|
||||||
* cache for the given key.
|
* cache for the given key.
|
||||||
* <p>
|
* <p>
|
||||||
* <strong>NOTE:</strong> A call to this method that returns <code>true</code>
|
* <strong>NOTE:</strong> A call to this method that returns <code>true</code>
|
||||||
* should always be matched with a call to {@link #releasePutFromLoadLock(Object)}.
|
* should always be matched with a call to {@link #releasePutFromLoadLock(Object, Lock)}.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param key the key
|
* @param key the key
|
||||||
*
|
*
|
||||||
* @return <code>true</code> if the lock is acquired and the cache put
|
* @return <code>AcquiredLock</code> if the lock is acquired and the cache put
|
||||||
* can proceed; <code>false</code> if the data should not be cached
|
* can proceed; <code>null</code> if the data should not be cached
|
||||||
*/
|
*/
|
||||||
public boolean acquirePutFromLoadLock(Object key) {
|
public Lock acquirePutFromLoadLock(Object key) {
|
||||||
boolean valid = false;
|
boolean valid = false;
|
||||||
boolean locked = false;
|
boolean locked = false;
|
||||||
final long now = System.currentTimeMillis();
|
long now = Long.MIN_VALUE;
|
||||||
|
|
||||||
try {
|
PendingPutMap pending = pendingPuts.get( key );
|
||||||
final PendingPutMap pending = pendingPuts.get( key );
|
for (;;) {
|
||||||
if ( pending != null ) {
|
try {
|
||||||
locked = pending.acquireLock( 100, TimeUnit.MILLISECONDS );
|
if (pending != null) {
|
||||||
if ( locked ) {
|
locked = pending.acquireLock(100, TimeUnit.MILLISECONDS);
|
||||||
try {
|
if (locked) {
|
||||||
final PendingPut toCancel = pending.remove( getOwnerForPut() );
|
try {
|
||||||
if ( toCancel != null ) {
|
final PendingPut toCancel = pending.remove(getOwnerForPut());
|
||||||
valid = !toCancel.completed;
|
if (toCancel != null) {
|
||||||
toCancel.completed = true;
|
valid = !toCancel.completed;
|
||||||
|
toCancel.completed = true;
|
||||||
|
} else {
|
||||||
|
// this is a naked put
|
||||||
|
if (pending.hasInvalidator()) {
|
||||||
|
valid = false;
|
||||||
|
} else {
|
||||||
|
if (now == Long.MIN_VALUE) {
|
||||||
|
now = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
valid = now > pending.nakedPutsDeadline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid ? pending : null;
|
||||||
|
} finally {
|
||||||
|
if (!valid) {
|
||||||
|
pending.releaseLock();
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// oops, we have leaked record for this owner, but we don't want to wait here
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Key wasn't in pendingPuts, so either this is a "naked put"
|
||||||
|
// or regionRemoved has been called. Check if we can proceed
|
||||||
|
long invalidationTimestamp = this.invalidationTimestamp;
|
||||||
|
if (invalidationTimestamp != Long.MIN_VALUE) {
|
||||||
|
now = System.currentTimeMillis();
|
||||||
|
if (now > invalidationTimestamp) {
|
||||||
|
// time is +- monotonic se don't let other threads do the expensive currentTimeMillis()
|
||||||
|
invalidationUpdater.compareAndSet(this, invalidationTimestamp, Long.MIN_VALUE);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
if ( !valid ) {
|
PendingPut pendingPut = new PendingPut(getOwnerForPut());
|
||||||
pending.releaseLock();
|
pending = new PendingPutMap(pendingPut);
|
||||||
locked = false;
|
PendingPutMap existing = pendingPuts.putIfAbsent(key, pending);
|
||||||
}
|
if (existing != null) {
|
||||||
|
pending = existing;
|
||||||
}
|
}
|
||||||
|
// continue in next loop with lock acquisition
|
||||||
}
|
}
|
||||||
}
|
} catch (Throwable t) {
|
||||||
else {
|
if (locked) {
|
||||||
// Key wasn't in pendingPuts, so either this is a "naked put"
|
pending.releaseLock();
|
||||||
// or regionRemoved has been called. Check if we can proceed
|
}
|
||||||
if ( now > invalidationTimestamp ) {
|
|
||||||
final Long removedTime = recentRemovals.get( key );
|
if (t instanceof RuntimeException) {
|
||||||
if ( removedTime == null || now > removedTime ) {
|
throw (RuntimeException) t;
|
||||||
// It's legal to proceed. But we have to record this key
|
} else if (t instanceof Error) {
|
||||||
// in pendingPuts so releasePutFromLoadLock can find it.
|
throw (Error) t;
|
||||||
// To do this we basically simulate a normal "register
|
} else {
|
||||||
// then acquire lock" pattern
|
throw new RuntimeException(t);
|
||||||
registerPendingPut( key );
|
|
||||||
locked = acquirePutFromLoadLock( key );
|
|
||||||
valid = locked;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
|
||||||
if ( locked ) {
|
|
||||||
final PendingPutMap toRelease = pendingPuts.get( key );
|
|
||||||
if ( toRelease != null ) {
|
|
||||||
toRelease.releaseLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( t instanceof RuntimeException ) {
|
|
||||||
throw (RuntimeException) t;
|
|
||||||
}
|
|
||||||
else if ( t instanceof Error ) {
|
|
||||||
throw (Error) t;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new RuntimeException( t );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return valid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -253,87 +263,16 @@ public class PutFromLoadValidator {
|
||||||
*
|
*
|
||||||
* @param key the key
|
* @param key the key
|
||||||
*/
|
*/
|
||||||
public void releasePutFromLoadLock(Object key) {
|
public void releasePutFromLoadLock(Object key, Lock lock) {
|
||||||
final PendingPutMap pending = pendingPuts.get( key );
|
final PendingPutMap pending = (PendingPutMap) lock;
|
||||||
if ( pending != null ) {
|
if ( pending != null ) {
|
||||||
if ( pending.size() == 0 ) {
|
if ( pending.canRemove() ) {
|
||||||
pendingPuts.remove( key, pending );
|
pendingPuts.remove( key, pending );
|
||||||
}
|
}
|
||||||
pending.releaseLock();
|
pending.releaseLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates any {@link #registerPendingPut(Object) previously registered pending puts} ensuring a subsequent call to
|
|
||||||
* {@link #acquirePutFromLoadLock(Object)} will return <code>false</code>. <p> This method will block until any
|
|
||||||
* concurrent thread that has {@link #acquirePutFromLoadLock(Object) acquired the putFromLoad lock} for the given key
|
|
||||||
* has released the lock. This allows the caller to be certain the putFromLoad will not execute after this method
|
|
||||||
* returns, possibly caching stale data. </p>
|
|
||||||
*
|
|
||||||
* @param key key identifying data whose pending puts should be invalidated
|
|
||||||
*
|
|
||||||
* @return <code>true</code> if the invalidation was successful; <code>false</code> if a problem occured (which the
|
|
||||||
* caller should treat as an exception condition)
|
|
||||||
*/
|
|
||||||
public boolean invalidateKey(Object key) {
|
|
||||||
boolean success = true;
|
|
||||||
|
|
||||||
// Invalidate any pending puts
|
|
||||||
final PendingPutMap pending = pendingPuts.get( key );
|
|
||||||
if ( pending != null ) {
|
|
||||||
// This lock should be available very quickly, but we'll be
|
|
||||||
// very patient waiting for it as callers should treat not
|
|
||||||
// acquiring it as an exception condition
|
|
||||||
if ( pending.acquireLock( 60, TimeUnit.SECONDS ) ) {
|
|
||||||
try {
|
|
||||||
pending.invalidate();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
pending.releaseLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record when this occurred to invalidate later naked puts
|
|
||||||
final RecentRemoval removal = new RecentRemoval( key, this.nakedPutInvalidationPeriod );
|
|
||||||
recentRemovals.put( key, removal.timestamp );
|
|
||||||
|
|
||||||
// Don't let recentRemovals map become a memory leak
|
|
||||||
RecentRemoval toClean = null;
|
|
||||||
final boolean attemptClean = removal.timestamp > 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 ) ) {
|
|
||||||
// Oops; removed the wrong timestamp; restore it
|
|
||||||
recentRemovals.putIfAbsent( toClean.key, cleaned );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates all {@link #registerPendingPut(Object) previously registered pending puts} ensuring a subsequent call to
|
* Invalidates all {@link #registerPendingPut(Object) previously registered pending puts} ensuring a subsequent call to
|
||||||
* {@link #acquirePutFromLoadLock(Object)} will return <code>false</code>. <p> This method will block until any
|
* {@link #acquirePutFromLoadLock(Object)} will return <code>false</code>. <p> This method will block until any
|
||||||
|
@ -345,15 +284,16 @@ public class PutFromLoadValidator {
|
||||||
* caller should treat as an exception condition)
|
* caller should treat as an exception condition)
|
||||||
*/
|
*/
|
||||||
public boolean invalidateRegion() {
|
public boolean invalidateRegion() {
|
||||||
|
// TODO: not sure what happens with locks acquired *after* calling this method but before
|
||||||
boolean ok = false;
|
// the actual invalidation
|
||||||
invalidationTimestamp = System.currentTimeMillis() + this.nakedPutInvalidationPeriod;
|
boolean ok = true;
|
||||||
|
invalidationUpdater.set(this, System.currentTimeMillis() + nakedPutInvalidationPeriod);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Acquire the lock for each entry to ensure any ongoing
|
// Acquire the lock for each entry to ensure any ongoing
|
||||||
// work associated with it is completed before we return
|
// work associated with it is completed before we return
|
||||||
for ( PendingPutMap entry : pendingPuts.values() ) {
|
for ( Iterator<PendingPutMap> it = pendingPuts.values().iterator(); it.hasNext(); ) {
|
||||||
|
PendingPutMap entry = it.next();
|
||||||
if ( entry.acquireLock( 60, TimeUnit.SECONDS ) ) {
|
if ( entry.acquireLock( 60, TimeUnit.SECONDS ) ) {
|
||||||
try {
|
try {
|
||||||
entry.invalidate();
|
entry.invalidate();
|
||||||
|
@ -361,30 +301,15 @@ public class PutFromLoadValidator {
|
||||||
finally {
|
finally {
|
||||||
entry.releaseLock();
|
entry.releaseLock();
|
||||||
}
|
}
|
||||||
|
it.remove();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
removalsLock.lock();
|
|
||||||
try {
|
|
||||||
recentRemovals.clear();
|
|
||||||
removalsQueue.clear();
|
|
||||||
|
|
||||||
ok = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
removalsLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
earliestRemovalTimestamp = invalidationTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
@ -395,8 +320,7 @@ public class PutFromLoadValidator {
|
||||||
* wherein it is expected that a database read plus cache put will occur. Calling this method allows the validator to
|
* wherein it is expected that a database read plus cache put will occur. Calling this method allows the validator to
|
||||||
* treat the subsequent <code>acquirePutFromLoadLock</code> as if the database read occurred when this method was
|
* treat the subsequent <code>acquirePutFromLoadLock</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
|
* 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
|
* notifications.
|
||||||
* not valid if any relevant removal has occurred within {@link #NAKED_PUT_INVALIDATION_PERIOD} milliseconds.
|
|
||||||
*
|
*
|
||||||
* @param key key that will be used for subsequent cache put
|
* @param key key that will be used for subsequent cache put
|
||||||
*/
|
*/
|
||||||
|
@ -404,50 +328,83 @@ public class PutFromLoadValidator {
|
||||||
final PendingPut pendingPut = new PendingPut( getOwnerForPut() );
|
final PendingPut pendingPut = new PendingPut( getOwnerForPut() );
|
||||||
final PendingPutMap pendingForKey = new PendingPutMap( pendingPut );
|
final PendingPutMap pendingForKey = new PendingPutMap( pendingPut );
|
||||||
|
|
||||||
for (; ; ) {
|
final PendingPutMap existing = pendingPuts.putIfAbsent( key, pendingForKey );
|
||||||
final PendingPutMap existing = pendingPuts.putIfAbsent( key, pendingForKey );
|
if ( existing != null ) {
|
||||||
if ( existing != null ) {
|
if ( existing.acquireLock( 10, TimeUnit.SECONDS ) ) {
|
||||||
if ( existing.acquireLock( 10, TimeUnit.SECONDS ) ) {
|
try {
|
||||||
|
if ( !existing.hasInvalidator() ) {
|
||||||
try {
|
existing.put(pendingPut);
|
||||||
existing.put( pendingPut );
|
|
||||||
final PendingPutMap doublecheck = pendingPuts.putIfAbsent( key, existing );
|
|
||||||
if ( doublecheck == null || doublecheck == existing ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// else we hit a race and need to loop to try again
|
|
||||||
}
|
}
|
||||||
finally {
|
} finally {
|
||||||
existing.releaseLock();
|
existing.releaseLock();
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Can't get the lock; when we come back we'll be a "naked put"
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// normal case
|
// Can't get the lock; when we come back we'll be a "naked put"
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------- Protected
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only for use by unit tests; may be removed at any time
|
* Invalidates any {@link #registerPendingPut(Object) previously registered pending puts}
|
||||||
|
* and disables further registrations ensuring a subsequent call to {@link #acquirePutFromLoadLock(Object)}
|
||||||
|
* will return <code>false</code>. <p> This method will block until any concurrent thread that has
|
||||||
|
* {@link #acquirePutFromLoadLock(Object) acquired the putFromLoad lock} for the given key
|
||||||
|
* has released the lock. This allows the caller to be certain the putFromLoad will not execute after this method
|
||||||
|
* returns, possibly caching stale data. </p>
|
||||||
|
* After this transaction completes, {@link #endInvalidatingKey(Object)} needs to be called }
|
||||||
|
*
|
||||||
|
* @param key key identifying data whose pending puts should be invalidated
|
||||||
|
*
|
||||||
|
* @return <code>true</code> if the invalidation was successful; <code>false</code> if a problem occured (which the
|
||||||
|
* caller should treat as an exception condition)
|
||||||
*/
|
*/
|
||||||
protected int getRemovalQueueLength() {
|
public boolean beginInvalidatingKey(Object key) {
|
||||||
removalsLock.lock();
|
PendingPutMap pending = new PendingPutMap(null);
|
||||||
try {
|
PendingPutMap prev = pendingPuts.putIfAbsent(key, pending);
|
||||||
return removalsQueue.size();
|
if (prev != null) {
|
||||||
|
pending = prev;
|
||||||
}
|
}
|
||||||
finally {
|
if (pending.acquireLock(60, TimeUnit.SECONDS)) {
|
||||||
removalsLock.unlock();
|
try {
|
||||||
|
pending.invalidate();
|
||||||
|
pending.addInvalidator(getOwnerForPut(), System.currentTimeMillis() + nakedPutInvalidationPeriod);
|
||||||
|
} finally {
|
||||||
|
pending.releaseLock();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the transaction completes, allowing caching of entries. It is possible that this method
|
||||||
|
* is called without previous invocation of {@link #beginInvalidatingKey(Object)}, then it should be noop.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean endInvalidatingKey(Object key) {
|
||||||
|
PendingPutMap pending = pendingPuts.get(key);
|
||||||
|
if (pending == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (pending.acquireLock(60, TimeUnit.SECONDS)) {
|
||||||
|
try {
|
||||||
|
pending.removeInvalidator(getOwnerForPut());
|
||||||
|
// we can't remove the pending put yet because we wait for naked puts
|
||||||
|
// pendingPuts should be configured with maxIdle time so won't have memory leak
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
pending.releaseLock();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Private
|
// ---------------------------------------------------------------- Private
|
||||||
|
|
||||||
private Object getOwnerForPut() {
|
private Object getOwnerForPut() {
|
||||||
|
@ -470,10 +427,13 @@ public class PutFromLoadValidator {
|
||||||
* <p/>
|
* <p/>
|
||||||
* This class is NOT THREAD SAFE. All operations on it must be performed with the lock held.
|
* This class is NOT THREAD SAFE. All operations on it must be performed with the lock held.
|
||||||
*/
|
*/
|
||||||
private static class PendingPutMap {
|
private static class PendingPutMap extends Lock {
|
||||||
private PendingPut singlePendingPut;
|
private PendingPut singlePendingPut;
|
||||||
private Map<Object, PendingPut> fullMap;
|
private Map<Object, PendingPut> fullMap;
|
||||||
private final Lock lock = new ReentrantLock();
|
private final java.util.concurrent.locks.Lock lock = new ReentrantLock();
|
||||||
|
private Object singleInvalidator;
|
||||||
|
private Set<Object> invalidators;
|
||||||
|
private long nakedPutsDeadline = Long.MIN_VALUE;
|
||||||
|
|
||||||
PendingPutMap(PendingPut singleItem) {
|
PendingPutMap(PendingPut singleItem) {
|
||||||
this.singlePendingPut = singleItem;
|
this.singlePendingPut = singleItem;
|
||||||
|
@ -546,6 +506,41 @@ public class PutFromLoadValidator {
|
||||||
fullMap = null;
|
fullMap = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addInvalidator(Object invalidator, long deadline) {
|
||||||
|
if (invalidators == null) {
|
||||||
|
if (singleInvalidator == null) {
|
||||||
|
singleInvalidator = invalidator;
|
||||||
|
} else {
|
||||||
|
invalidators = new HashSet<Object>();
|
||||||
|
invalidators.add(singleInvalidator);
|
||||||
|
invalidators.add(invalidator);
|
||||||
|
singleInvalidator = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidators.add(invalidator);
|
||||||
|
}
|
||||||
|
nakedPutsDeadline = Math.max(nakedPutsDeadline, deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasInvalidator() {
|
||||||
|
return singleInvalidator != null || (invalidators != null && !invalidators.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeInvalidator(Object invalidator) {
|
||||||
|
if (invalidators == null) {
|
||||||
|
if (singleInvalidator != null && singleInvalidator.equals(invalidator)) {
|
||||||
|
singleInvalidator = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidators.remove(invalidator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canRemove() {
|
||||||
|
return size() == 0 && !hasInvalidator() &&
|
||||||
|
(nakedPutsDeadline == Long.MIN_VALUE || nakedPutsDeadline < System.currentTimeMillis());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PendingPut {
|
private static class PendingPut {
|
||||||
|
@ -556,15 +551,4 @@ public class PutFromLoadValidator {
|
||||||
this.owner = owner;
|
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 = System.currentTimeMillis() + nakedPutInvalidationPeriod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.cache.infinispan.access;
|
||||||
import org.hibernate.cache.CacheException;
|
import org.hibernate.cache.CacheException;
|
||||||
import org.hibernate.cache.infinispan.impl.BaseRegion;
|
import org.hibernate.cache.infinispan.impl.BaseRegion;
|
||||||
import org.hibernate.cache.infinispan.util.Caches;
|
import org.hibernate.cache.infinispan.util.Caches;
|
||||||
|
|
||||||
import org.infinispan.AdvancedCache;
|
import org.infinispan.AdvancedCache;
|
||||||
import org.infinispan.util.logging.Log;
|
import org.infinispan.util.logging.Log;
|
||||||
import org.infinispan.util.logging.LogFactory;
|
import org.infinispan.util.logging.LogFactory;
|
||||||
|
@ -111,7 +110,8 @@ public class TransactionalAccessDelegate {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !putValidator.acquirePutFromLoadLock( key ) ) {
|
PutFromLoadValidator.Lock lock = putValidator.acquirePutFromLoadLock(key);
|
||||||
|
if ( lock == null) {
|
||||||
if ( TRACE_ENABLED ) {
|
if ( TRACE_ENABLED ) {
|
||||||
log.tracef( "Put from load lock not acquired for key %s", key );
|
log.tracef( "Put from load lock not acquired for key %s", key );
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ public class TransactionalAccessDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
putValidator.releasePutFromLoadLock( key );
|
putValidator.releasePutFromLoadLock( key, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -185,7 +185,7 @@ public class TransactionalAccessDelegate {
|
||||||
* @throws CacheException if removing the cached item fails
|
* @throws CacheException if removing the cached item fails
|
||||||
*/
|
*/
|
||||||
public void remove(Object key) throws CacheException {
|
public void remove(Object key) throws CacheException {
|
||||||
if ( !putValidator.invalidateKey( key ) ) {
|
if ( !putValidator.beginInvalidatingKey(key)) {
|
||||||
throw new CacheException(
|
throw new CacheException(
|
||||||
"Failed to invalidate pending putFromLoad calls for key " + key + " from region " + region.getName()
|
"Failed to invalidate pending putFromLoad calls for key " + key + " from region " + region.getName()
|
||||||
);
|
);
|
||||||
|
@ -216,7 +216,7 @@ public class TransactionalAccessDelegate {
|
||||||
* @throws CacheException if evicting the item fails
|
* @throws CacheException if evicting the item fails
|
||||||
*/
|
*/
|
||||||
public void evict(Object key) throws CacheException {
|
public void evict(Object key) throws CacheException {
|
||||||
if ( !putValidator.invalidateKey( key ) ) {
|
if ( !putValidator.beginInvalidatingKey(key)) {
|
||||||
throw new CacheException(
|
throw new CacheException(
|
||||||
"Failed to invalidate pending putFromLoad calls for key " + key + " from region " + region.getName()
|
"Failed to invalidate pending putFromLoad calls for key " + key + " from region " + region.getName()
|
||||||
);
|
);
|
||||||
|
@ -240,4 +240,19 @@ public class TransactionalAccessDelegate {
|
||||||
Caches.broadcastEvictAll( cache );
|
Caches.broadcastEvictAll( cache );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when we have finished the attempted update/delete (which may or
|
||||||
|
* may not have been successful), after transaction completion. This method
|
||||||
|
* is used by "asynchronous" concurrency strategies.
|
||||||
|
*
|
||||||
|
* @param key The item key
|
||||||
|
* @throws org.hibernate.cache.CacheException Propogated from underlying {@link org.hibernate.cache.spi.Region}
|
||||||
|
*/
|
||||||
|
public void unlockItem(Object key) throws CacheException {
|
||||||
|
if ( !putValidator.endInvalidatingKey(key) ) {
|
||||||
|
// TODO: localization
|
||||||
|
log.warn("Failed to end invalidating pending putFromLoad calls for key " + key + " from region "
|
||||||
|
+ region.getName() + "; the key won't be cached in the future.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ class TransactionalAccess implements CollectionRegionAccessStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
||||||
|
delegate.unlockItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockRegion(SoftLock lock) throws CacheException {
|
public void unlockRegion(SoftLock lock) throws CacheException {
|
||||||
|
|
|
@ -84,6 +84,7 @@ class TransactionalAccess implements EntityRegionAccessStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
||||||
|
delegate.unlockItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unlockRegion(SoftLock lock) throws CacheException {
|
public void unlockRegion(SoftLock lock) throws CacheException {
|
||||||
|
|
|
@ -89,6 +89,7 @@ class TransactionalAccess implements NaturalIdRegionAccessStrategy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
public void unlockItem(SessionImplementor session, Object key, SoftLock lock) throws CacheException {
|
||||||
|
delegate.unlockItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,8 +20,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.hibernate.cache.infinispan.access.PutFromLoadValidator;
|
import org.hibernate.cache.infinispan.access.PutFromLoadValidator;
|
||||||
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeJtaTransactionManagerImpl;
|
import org.hibernate.test.cache.infinispan.functional.cluster.DualNodeJtaTransactionManagerImpl;
|
||||||
import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup;
|
import org.hibernate.test.cache.infinispan.util.InfinispanTestingSetup;
|
||||||
import org.infinispan.AdvancedCache;
|
|
||||||
import org.infinispan.manager.EmbeddedCacheManager;
|
|
||||||
import org.infinispan.test.CacheManagerCallable;
|
import org.infinispan.test.CacheManagerCallable;
|
||||||
import org.infinispan.test.fwk.TestCacheManagerFactory;
|
import org.infinispan.test.fwk.TestCacheManagerFactory;
|
||||||
import org.infinispan.util.logging.Log;
|
import org.infinispan.util.logging.Log;
|
||||||
|
@ -32,8 +30,9 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.infinispan.test.TestingUtil.withCacheManager;
|
import static org.infinispan.test.TestingUtil.withCacheManager;
|
||||||
|
import static org.infinispan.test.TestingUtil.withTx;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
@ -87,28 +86,13 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
TestCacheManagerFactory.createCacheManager(false)) {
|
TestCacheManagerFactory.createCacheManager(false)) {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
try {
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
transactional ? tm : null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
||||||
transactional ? tm : null,
|
exec(transactional, new NakedPut(testee, true));
|
||||||
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
|
||||||
if (transactional) {
|
|
||||||
tm.begin();
|
|
||||||
}
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
|
||||||
try {
|
|
||||||
assertTrue(lockable);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (lockable) {
|
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegisteredPut() throws Exception {
|
public void testRegisteredPut() throws Exception {
|
||||||
registeredPutTest(false);
|
registeredPutTest(false);
|
||||||
|
@ -124,29 +108,12 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
transactional ? tm : null,
|
transactional ? tm : null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
||||||
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
exec(transactional, new RegularPut(testee));
|
||||||
try {
|
|
||||||
if (transactional) {
|
|
||||||
tm.begin();
|
|
||||||
}
|
|
||||||
testee.registerPendingPut(KEY1);
|
|
||||||
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
|
||||||
try {
|
|
||||||
assertTrue(lockable);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (lockable) {
|
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNakedPutAfterKeyRemoval() throws Exception {
|
public void testNakedPutAfterKeyRemoval() throws Exception {
|
||||||
nakedPutAfterRemovalTest(false, false);
|
nakedPutAfterRemovalTest(false, false);
|
||||||
|
@ -171,34 +138,15 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
transactional ? tm : null,
|
transactional ? tm : null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
||||||
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
Invalidation invalidation = new Invalidation(testee, removeRegion);
|
||||||
if (removeRegion) {
|
NakedPut nakedPut = new NakedPut(testee, false);
|
||||||
testee.invalidateRegion();
|
exec(transactional, invalidation, nakedPut);
|
||||||
} else {
|
|
||||||
testee.invalidateKey(KEY1);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (transactional) {
|
|
||||||
tm.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
|
||||||
try {
|
|
||||||
assertFalse(lockable);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (lockable) {
|
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRegisteredPutAfterKeyRemoval() throws Exception {
|
public void testRegisteredPutAfterKeyRemoval() throws Exception {
|
||||||
registeredPutAfterRemovalTest(false, false);
|
registeredPutAfterRemovalTest(false, false);
|
||||||
|
@ -223,31 +171,10 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
transactional ? tm : null,
|
transactional ? tm : null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
||||||
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
Invalidation invalidation = new Invalidation(testee, removeRegion);
|
||||||
if (removeRegion) {
|
RegularPut regularPut = new RegularPut(testee);
|
||||||
testee.invalidateRegion();
|
exec(transactional, invalidation, regularPut);
|
||||||
} else {
|
|
||||||
testee.invalidateKey(KEY1);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (transactional) {
|
|
||||||
tm.begin();
|
|
||||||
}
|
|
||||||
testee.registerPendingPut(KEY1);
|
|
||||||
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
|
||||||
try {
|
|
||||||
assertTrue(lockable);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (lockable) {
|
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -277,8 +204,7 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
transactional ? tm : null,
|
transactional ? tm : null, PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
||||||
PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD);
|
|
||||||
try {
|
try {
|
||||||
if (transactional) {
|
if (transactional) {
|
||||||
tm.begin();
|
tm.begin();
|
||||||
|
@ -287,17 +213,18 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
if (removeRegion) {
|
if (removeRegion) {
|
||||||
testee.invalidateRegion();
|
testee.invalidateRegion();
|
||||||
} else {
|
} else {
|
||||||
testee.invalidateKey(KEY1);
|
testee.beginInvalidatingKey(KEY1);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
PutFromLoadValidator.Lock lock = testee.acquirePutFromLoadLock(KEY1);
|
||||||
try {
|
try {
|
||||||
assertFalse(lockable);
|
assertNull(lock);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (lockable) {
|
if (lock != null) {
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
testee.releasePutFromLoadLock(KEY1, lock);
|
||||||
}
|
}
|
||||||
|
testee.endInvalidatingKey(KEY1);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -305,6 +232,7 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDelayedNakedPutAfterKeyRemoval() throws Exception {
|
public void testDelayedNakedPutAfterKeyRemoval() throws Exception {
|
||||||
delayedNakedPutAfterRemovalTest(false, false);
|
delayedNakedPutAfterRemovalTest(false, false);
|
||||||
|
@ -329,12 +257,13 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
TestCacheManagerFactory.createCacheManager(false)) {
|
TestCacheManagerFactory.createCacheManager(false)) {
|
||||||
@Override
|
@Override
|
||||||
public void call() {
|
public void call() {
|
||||||
PutFromLoadValidator testee = new TestValidator(cm.getCache().getAdvancedCache(), cm,
|
PutFromLoadValidator testee = new PutFromLoadValidator(cm.getCache().getAdvancedCache(), cm,
|
||||||
transactional ? tm : null, 100);
|
transactional ? tm : null, 100);
|
||||||
if (removeRegion) {
|
if (removeRegion) {
|
||||||
testee.invalidateRegion();
|
testee.invalidateRegion();
|
||||||
} else {
|
} else {
|
||||||
testee.invalidateKey(KEY1);
|
testee.beginInvalidatingKey(KEY1);
|
||||||
|
testee.endInvalidatingKey(KEY1);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (transactional) {
|
if (transactional) {
|
||||||
|
@ -342,12 +271,12 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
}
|
}
|
||||||
Thread.sleep(110);
|
Thread.sleep(110);
|
||||||
|
|
||||||
boolean lockable = testee.acquirePutFromLoadLock(KEY1);
|
PutFromLoadValidator.Lock lock = testee.acquirePutFromLoadLock(KEY1);
|
||||||
try {
|
try {
|
||||||
assertTrue(lockable);
|
assertNotNull(lock);
|
||||||
} finally {
|
} finally {
|
||||||
if (lockable) {
|
if (lock != null) {
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
testee.releasePutFromLoadLock(KEY1, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -389,12 +318,13 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
testee.registerPendingPut(KEY1);
|
testee.registerPendingPut(KEY1);
|
||||||
registeredLatch.countDown();
|
registeredLatch.countDown();
|
||||||
registeredLatch.await(5, TimeUnit.SECONDS);
|
registeredLatch.await(5, TimeUnit.SECONDS);
|
||||||
if (testee.acquirePutFromLoadLock(KEY1)) {
|
PutFromLoadValidator.Lock lock = testee.acquirePutFromLoadLock(KEY1);
|
||||||
|
if (lock != null) {
|
||||||
try {
|
try {
|
||||||
log.trace("Put from load lock acquired for key = " + KEY1);
|
log.trace("Put from load lock acquired for key = " + KEY1);
|
||||||
success.incrementAndGet();
|
success.incrementAndGet();
|
||||||
} finally {
|
} finally {
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
testee.releasePutFromLoadLock(KEY1, lock);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.trace("Unable to acquired putFromLoad lock for key = " + KEY1);
|
log.trace("Unable to acquired putFromLoad lock for key = " + KEY1);
|
||||||
|
@ -453,7 +383,8 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
Callable<Boolean> pferCallable = new Callable<Boolean>() {
|
Callable<Boolean> pferCallable = new Callable<Boolean>() {
|
||||||
public Boolean call() throws Exception {
|
public Boolean call() throws Exception {
|
||||||
testee.registerPendingPut(KEY1);
|
testee.registerPendingPut(KEY1);
|
||||||
if (testee.acquirePutFromLoadLock(KEY1)) {
|
PutFromLoadValidator.Lock lock = testee.acquirePutFromLoadLock(KEY1);
|
||||||
|
if (lock != null) {
|
||||||
try {
|
try {
|
||||||
removeLatch.countDown();
|
removeLatch.countDown();
|
||||||
pferLatch.await();
|
pferLatch.await();
|
||||||
|
@ -461,7 +392,7 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
testee.releasePutFromLoadLock(KEY1);
|
testee.releasePutFromLoadLock(KEY1, lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Boolean.FALSE;
|
return Boolean.FALSE;
|
||||||
|
@ -472,7 +403,7 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
public Void call() throws Exception {
|
public Void call() throws Exception {
|
||||||
removeLatch.await();
|
removeLatch.await();
|
||||||
if (keyOnly) {
|
if (keyOnly) {
|
||||||
testee.invalidateKey(KEY1);
|
testee.beginInvalidatingKey(KEY1);
|
||||||
} else {
|
} else {
|
||||||
testee.invalidateRegion();
|
testee.invalidateRegion();
|
||||||
}
|
}
|
||||||
|
@ -505,18 +436,104 @@ public class PutFromLoadValidatorUnitTestCase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestValidator extends PutFromLoadValidator {
|
protected void exec(boolean transactional, Callable<?>... callables) {
|
||||||
|
try {
|
||||||
|
if (transactional) {
|
||||||
|
for (Callable<?> c : callables) {
|
||||||
|
withTx(tm, c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Callable<?> c : callables) {
|
||||||
|
c.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected TestValidator(AdvancedCache cache, EmbeddedCacheManager cm,
|
private class Invalidation implements Callable<Void> {
|
||||||
TransactionManager transactionManager,
|
private PutFromLoadValidator putFromLoadValidator;
|
||||||
long nakedPutInvalidationPeriod) {
|
private boolean removeRegion;
|
||||||
super(cache, cm, transactionManager, nakedPutInvalidationPeriod);
|
|
||||||
|
public Invalidation(PutFromLoadValidator putFromLoadValidator, boolean removeRegion) {
|
||||||
|
this.putFromLoadValidator = putFromLoadValidator;
|
||||||
|
this.removeRegion = removeRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRemovalQueueLength() {
|
public Void call() throws Exception {
|
||||||
return super.getRemovalQueueLength();
|
if (removeRegion) {
|
||||||
|
boolean success = putFromLoadValidator.invalidateRegion();
|
||||||
|
assertTrue(success);
|
||||||
|
} else {
|
||||||
|
boolean success = putFromLoadValidator.beginInvalidatingKey(KEY1);
|
||||||
|
assertTrue(success);
|
||||||
|
success = putFromLoadValidator.endInvalidatingKey(KEY1);
|
||||||
|
assertTrue(success);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegularPut implements Callable<Void> {
|
||||||
|
private PutFromLoadValidator putFromLoadValidator;
|
||||||
|
|
||||||
|
public RegularPut(PutFromLoadValidator putFromLoadValidator) {
|
||||||
|
this.putFromLoadValidator = putFromLoadValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void call() throws Exception {
|
||||||
|
try {
|
||||||
|
putFromLoadValidator.registerPendingPut(KEY1);
|
||||||
|
|
||||||
|
PutFromLoadValidator.Lock lock = putFromLoadValidator.acquirePutFromLoadLock(KEY1);
|
||||||
|
try {
|
||||||
|
assertNotNull(lock);
|
||||||
|
} finally {
|
||||||
|
if (lock != null) {
|
||||||
|
putFromLoadValidator.releasePutFromLoadLock(KEY1, lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NakedPut implements Callable<Void> {
|
||||||
|
private final PutFromLoadValidator testee;
|
||||||
|
private final boolean expectSuccess;
|
||||||
|
|
||||||
|
public NakedPut(PutFromLoadValidator testee, boolean expectSuccess) {
|
||||||
|
this.testee = testee;
|
||||||
|
this.expectSuccess = expectSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void call() throws Exception {
|
||||||
|
try {
|
||||||
|
PutFromLoadValidator.Lock lock = testee.acquirePutFromLoadLock(KEY1);
|
||||||
|
try {
|
||||||
|
if (expectSuccess) {
|
||||||
|
assertNotNull(lock);
|
||||||
|
} else {
|
||||||
|
assertNull(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (lock != null) {
|
||||||
|
testee.releasePutFromLoadLock(KEY1, lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,8 +161,8 @@ public abstract class AbstractCollectionRegionAccessStrategyTestCase extends Abs
|
||||||
PutFromLoadValidator validator = new PutFromLoadValidator(remoteCollectionRegion.getCache(), cm,
|
PutFromLoadValidator validator = new PutFromLoadValidator(remoteCollectionRegion.getCache(), cm,
|
||||||
remoteTm, 20000) {
|
remoteTm, 20000) {
|
||||||
@Override
|
@Override
|
||||||
public boolean acquirePutFromLoadLock(Object key) {
|
public Lock acquirePutFromLoadLock(Object key) {
|
||||||
boolean acquired = super.acquirePutFromLoadLock( key );
|
Lock lock = super.acquirePutFromLoadLock( key );
|
||||||
try {
|
try {
|
||||||
removeLatch.countDown();
|
removeLatch.countDown();
|
||||||
pferLatch.await( 2, TimeUnit.SECONDS );
|
pferLatch.await( 2, TimeUnit.SECONDS );
|
||||||
|
@ -175,7 +175,7 @@ public abstract class AbstractCollectionRegionAccessStrategyTestCase extends Abs
|
||||||
log.error( "Error", e );
|
log.error( "Error", e );
|
||||||
throw new RuntimeException( "Error", e );
|
throw new RuntimeException( "Error", e );
|
||||||
}
|
}
|
||||||
return acquired;
|
return lock;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,33 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.test.cache.infinispan.functional;
|
package org.hibernate.test.cache.infinispan.functional;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Phaser;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.hibernate.PessimisticLockException;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.cache.infinispan.InfinispanRegionFactory;
|
||||||
|
import org.hibernate.cache.infinispan.entity.EntityRegionImpl;
|
||||||
|
import org.hibernate.cache.spi.Region;
|
||||||
import org.hibernate.stat.SecondLevelCacheStatistics;
|
import org.hibernate.stat.SecondLevelCacheStatistics;
|
||||||
import org.hibernate.stat.Statistics;
|
import org.hibernate.stat.Statistics;
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.infinispan.AdvancedCache;
|
||||||
|
import org.infinispan.commands.read.GetKeyValueCommand;
|
||||||
|
import org.infinispan.context.InvocationContext;
|
||||||
|
import org.infinispan.interceptors.base.BaseCustomInterceptor;
|
||||||
import org.infinispan.util.logging.Log;
|
import org.infinispan.util.logging.Log;
|
||||||
import org.infinispan.util.logging.LogFactory;
|
import org.infinispan.util.logging.LogFactory;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import static org.infinispan.test.TestingUtil.withTx;
|
import static org.infinispan.test.TestingUtil.withTx;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parent tests for both transactional and
|
* Parent tests for both transactional and
|
||||||
|
@ -122,4 +137,152 @@ public abstract class AbstractFunctionalTestCase extends SingleNodeTestCase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestForIssue(jiraKey = "HHH-9868")
|
||||||
|
public void testConcurrentRemoveAndPutFromLoad() throws Exception {
|
||||||
|
final Item item = new Item( "chris", "Chris's Item" );
|
||||||
|
|
||||||
|
withTx(tm, () -> {
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
s.persist(item);
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
Region region = sessionFactory().getSecondLevelCacheRegion(Item.class.getName());
|
||||||
|
|
||||||
|
Phaser deletePhaser = new Phaser(2);
|
||||||
|
Phaser getPhaser = new Phaser(2);
|
||||||
|
HookInterceptor hook = new HookInterceptor();
|
||||||
|
|
||||||
|
AdvancedCache entityCache = ((EntityRegionImpl) region).getCache();
|
||||||
|
AdvancedCache pendingPutsCache = entityCache.getCacheManager().getCache(
|
||||||
|
entityCache.getName() + "-" + InfinispanRegionFactory.PENDING_PUTS_CACHE_NAME).getAdvancedCache();
|
||||||
|
pendingPutsCache.addInterceptor(hook, 0);
|
||||||
|
|
||||||
|
Thread deleteThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
withTx(tm, () -> {
|
||||||
|
Session s = openSession();
|
||||||
|
log.trace("Session opened");
|
||||||
|
s.getTransaction().begin();
|
||||||
|
log.trace("TX started");
|
||||||
|
Item loadedItem = s.get(Item.class, item.getId());
|
||||||
|
assertNotNull(loadedItem);
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
log.trace("Item loaded");
|
||||||
|
s.delete(loadedItem);
|
||||||
|
log.trace("Item deleted");
|
||||||
|
s.getTransaction().commit();
|
||||||
|
log.trace("TX committed");
|
||||||
|
// start get-thread here
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
s.close();
|
||||||
|
log.trace("Session closed");
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, "delete-thread");
|
||||||
|
Thread getThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
withTx(tm, () -> {
|
||||||
|
Session s = openSession();
|
||||||
|
log.trace("Session opened");
|
||||||
|
s.getTransaction().begin();
|
||||||
|
log.trace("TX started");
|
||||||
|
// DB load should happen before the record is deleted,
|
||||||
|
// putFromLoad should happen after deleteThread ends
|
||||||
|
Item loadedItem = s.get(Item.class, item.getId());
|
||||||
|
assertNotNull(loadedItem);
|
||||||
|
s.close();
|
||||||
|
log.trace("Session closed");
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch (PessimisticLockException e) {
|
||||||
|
// If we end up here, database locks guard us against situation tested
|
||||||
|
// in this case and HHH-9868 cannot happen.
|
||||||
|
// (delete-thread has ITEMS table write-locked and we try to acquire read-lock)
|
||||||
|
try {
|
||||||
|
arriveAndAwait(getPhaser);
|
||||||
|
arriveAndAwait(getPhaser);
|
||||||
|
} catch (Exception e1) {
|
||||||
|
throw new RuntimeException(e1);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, "get-thread");
|
||||||
|
|
||||||
|
deleteThread.start();
|
||||||
|
// deleteThread loads the entity
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
withTx(tm, () -> {
|
||||||
|
sessionFactory().getCache().evictEntity(Item.class, item.getId());
|
||||||
|
assertFalse(sessionFactory().getCache().containsEntity(Item.class, item.getId()));
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
// delete thread invalidates PFER
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
// get thread gets the entity from DB
|
||||||
|
hook.block(getPhaser, getThread);
|
||||||
|
getThread.start();
|
||||||
|
arriveAndAwait(getPhaser);
|
||||||
|
arriveAndAwait(deletePhaser);
|
||||||
|
// delete thread finishes the remove from DB and cache
|
||||||
|
deleteThread.join();
|
||||||
|
hook.unblock();
|
||||||
|
arriveAndAwait(getPhaser);
|
||||||
|
// get thread puts the entry into cache
|
||||||
|
getThread.join();
|
||||||
|
|
||||||
|
withTx(tm, () -> {
|
||||||
|
Session s = openSession();
|
||||||
|
s.getTransaction().begin();
|
||||||
|
Item loadedItem = s.get(Item.class, item.getId());
|
||||||
|
assertNull(loadedItem);
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void arriveAndAwait(Phaser phaser) throws TimeoutException, InterruptedException {
|
||||||
|
phaser.awaitAdvanceInterruptibly(phaser.arrive(), 10, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HookInterceptor extends BaseCustomInterceptor {
|
||||||
|
Phaser phaser;
|
||||||
|
Thread thread;
|
||||||
|
|
||||||
|
public synchronized void block(Phaser phaser, Thread thread) {
|
||||||
|
this.phaser = phaser;
|
||||||
|
this.thread = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unblock() {
|
||||||
|
phaser = null;
|
||||||
|
thread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable {
|
||||||
|
Phaser phaser;
|
||||||
|
Thread thread;
|
||||||
|
synchronized (this) {
|
||||||
|
phaser = this.phaser;
|
||||||
|
thread = this.thread;
|
||||||
|
}
|
||||||
|
if (phaser != null && Thread.currentThread() == thread) {
|
||||||
|
arriveAndAwait(phaser);
|
||||||
|
arriveAndAwait(phaser);
|
||||||
|
}
|
||||||
|
return super.visitGetKeyValueCommand(ctx, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.hibernate.Cache;
|
import org.hibernate.Cache;
|
||||||
import org.hibernate.Criteria;
|
import org.hibernate.Criteria;
|
||||||
|
@ -19,12 +18,10 @@ import org.hibernate.Hibernate;
|
||||||
import org.hibernate.NaturalIdLoadAccess;
|
import org.hibernate.NaturalIdLoadAccess;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
import org.hibernate.cache.infinispan.access.PutFromLoadValidator;
|
|
||||||
import org.hibernate.cache.spi.entry.CacheEntry;
|
import org.hibernate.cache.spi.entry.CacheEntry;
|
||||||
import org.hibernate.criterion.Restrictions;
|
import org.hibernate.criterion.Restrictions;
|
||||||
import org.hibernate.stat.SecondLevelCacheStatistics;
|
import org.hibernate.stat.SecondLevelCacheStatistics;
|
||||||
import org.hibernate.stat.Statistics;
|
import org.hibernate.stat.Statistics;
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -954,7 +951,6 @@ public class BasicTransactionalTestCase extends AbstractFunctionalTestCase {
|
||||||
|
|
||||||
// TODO: Clear caches manually via cache manager (it's faster!!)
|
// TODO: Clear caches manually via cache manager (it's faster!!)
|
||||||
this.cleanupCache();
|
this.cleanupCache();
|
||||||
Thread.sleep(PutFromLoadValidator.NAKED_PUT_INVALIDATION_PERIOD + TimeUnit.SECONDS.toMillis(1));
|
|
||||||
stats.setStatisticsEnabled( true );
|
stats.setStatisticsEnabled( true );
|
||||||
stats.clear();
|
stats.clear();
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.test.cache.infinispan.functional.cluster;
|
package org.hibernate.test.cache.infinispan.functional.cluster;
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import javax.transaction.HeuristicMixedException;
|
import javax.transaction.HeuristicMixedException;
|
||||||
import javax.transaction.HeuristicRollbackException;
|
import javax.transaction.HeuristicRollbackException;
|
||||||
import javax.transaction.RollbackException;
|
import javax.transaction.RollbackException;
|
||||||
|
@ -23,6 +16,13 @@ import javax.transaction.Transaction;
|
||||||
import javax.transaction.xa.XAException;
|
import javax.transaction.xa.XAException;
|
||||||
import javax.transaction.xa.XAResource;
|
import javax.transaction.xa.XAResource;
|
||||||
import javax.transaction.xa.Xid;
|
import javax.transaction.xa.Xid;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.infinispan.util.logging.Log;
|
import org.infinispan.util.logging.Log;
|
||||||
import org.infinispan.util.logging.LogFactory;
|
import org.infinispan.util.logging.LogFactory;
|
||||||
|
@ -62,9 +62,11 @@ public class DualNodeJtaTransactionImpl implements Transaction {
|
||||||
} else {
|
} else {
|
||||||
status = Status.STATUS_PREPARING;
|
status = Status.STATUS_PREPARING;
|
||||||
|
|
||||||
for (int i = 0; i < synchronizations.size(); i++) {
|
if (synchronizations != null) {
|
||||||
Synchronization s = (Synchronization) synchronizations.get(i);
|
for (int i = 0; i < synchronizations.size(); i++) {
|
||||||
s.beforeCompletion();
|
Synchronization s = (Synchronization) synchronizations.get(i);
|
||||||
|
s.beforeCompletion();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!runXaResourcePrepare()) {
|
if (!runXaResourcePrepare()) {
|
||||||
|
@ -89,9 +91,11 @@ public class DualNodeJtaTransactionImpl implements Transaction {
|
||||||
|
|
||||||
status = Status.STATUS_COMMITTED;
|
status = Status.STATUS_COMMITTED;
|
||||||
|
|
||||||
for (int i = 0; i < synchronizations.size(); i++) {
|
if (synchronizations != null) {
|
||||||
Synchronization s = (Synchronization) synchronizations.get(i);
|
for (int i = 0; i < synchronizations.size(); i++) {
|
||||||
s.afterCompletion(status);
|
Synchronization s = (Synchronization) synchronizations.get(i);
|
||||||
|
s.afterCompletion(status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// status = Status.STATUS_NO_TRANSACTION;
|
// status = Status.STATUS_NO_TRANSACTION;
|
||||||
|
|
|
@ -23,41 +23,44 @@ import javax.transaction.TransactionManager;
|
||||||
*/
|
*/
|
||||||
public class XaTransactionManagerImpl implements TransactionManager {
|
public class XaTransactionManagerImpl implements TransactionManager {
|
||||||
private static final XaTransactionManagerImpl INSTANCE = new XaTransactionManagerImpl();
|
private static final XaTransactionManagerImpl INSTANCE = new XaTransactionManagerImpl();
|
||||||
private XaTransactionImpl currentTransaction;
|
private final ThreadLocal<XaTransactionImpl> currentTransaction = new ThreadLocal<>();
|
||||||
|
|
||||||
public static XaTransactionManagerImpl getInstance() {
|
public static XaTransactionManagerImpl getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getStatus() throws SystemException {
|
public int getStatus() throws SystemException {
|
||||||
|
XaTransactionImpl currentTransaction = this.currentTransaction.get();
|
||||||
return currentTransaction == null ? Status.STATUS_NO_TRANSACTION : currentTransaction.getStatus();
|
return currentTransaction == null ? Status.STATUS_NO_TRANSACTION : currentTransaction.getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transaction getTransaction() throws SystemException {
|
public Transaction getTransaction() throws SystemException {
|
||||||
return currentTransaction;
|
return currentTransaction.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public XaTransactionImpl getCurrentTransaction() {
|
public XaTransactionImpl getCurrentTransaction() {
|
||||||
return currentTransaction;
|
return currentTransaction.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void begin() throws NotSupportedException, SystemException {
|
public void begin() throws NotSupportedException, SystemException {
|
||||||
currentTransaction = new XaTransactionImpl(this);
|
if (currentTransaction.get() != null) throw new IllegalStateException("Transaction already started.");
|
||||||
|
currentTransaction.set(new XaTransactionImpl(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transaction suspend() throws SystemException {
|
public Transaction suspend() throws SystemException {
|
||||||
Transaction suspended = currentTransaction;
|
Transaction suspended = currentTransaction.get();
|
||||||
currentTransaction = null;
|
currentTransaction.remove();
|
||||||
return suspended;
|
return suspended;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resume(Transaction transaction) throws InvalidTransactionException, IllegalStateException,
|
public void resume(Transaction transaction) throws InvalidTransactionException, IllegalStateException,
|
||||||
SystemException {
|
SystemException {
|
||||||
currentTransaction = (XaTransactionImpl) transaction;
|
currentTransaction.set((XaTransactionImpl) transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
|
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
|
||||||
SecurityException, IllegalStateException, SystemException {
|
SecurityException, IllegalStateException, SystemException {
|
||||||
|
XaTransactionImpl currentTransaction = this.currentTransaction.get();
|
||||||
if (currentTransaction == null) {
|
if (currentTransaction == null) {
|
||||||
throw new IllegalStateException("no current transaction to commit");
|
throw new IllegalStateException("no current transaction to commit");
|
||||||
}
|
}
|
||||||
|
@ -65,6 +68,7 @@ public class XaTransactionManagerImpl implements TransactionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rollback() throws IllegalStateException, SecurityException, SystemException {
|
public void rollback() throws IllegalStateException, SecurityException, SystemException {
|
||||||
|
XaTransactionImpl currentTransaction = this.currentTransaction.get();
|
||||||
if (currentTransaction == null) {
|
if (currentTransaction == null) {
|
||||||
throw new IllegalStateException("no current transaction");
|
throw new IllegalStateException("no current transaction");
|
||||||
}
|
}
|
||||||
|
@ -72,6 +76,7 @@ public class XaTransactionManagerImpl implements TransactionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRollbackOnly() throws IllegalStateException, SystemException {
|
public void setRollbackOnly() throws IllegalStateException, SystemException {
|
||||||
|
XaTransactionImpl currentTransaction = this.currentTransaction.get();
|
||||||
if (currentTransaction == null) {
|
if (currentTransaction == null) {
|
||||||
throw new IllegalStateException("no current transaction");
|
throw new IllegalStateException("no current transaction");
|
||||||
}
|
}
|
||||||
|
@ -82,8 +87,6 @@ public class XaTransactionManagerImpl implements TransactionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
void endCurrent(Transaction transaction) {
|
void endCurrent(Transaction transaction) {
|
||||||
if (transaction == currentTransaction) {
|
currentTransaction.remove();
|
||||||
currentTransaction = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue