[hibernate-commits] Hibernate SVN: r17658 - core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Thu Oct 8 15:43:00 EDT 2009


Author: bstansberry at jboss.com
Date: 2009-10-08 15:43:00 -0400 (Thu, 08 Oct 2009)
New Revision: 17658

Modified:
   core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java
Log:
Fix code style

Modified: 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	2009-10-08 17:50:07 UTC (rev 17657)
+++ core/trunk/cache-jbosscache/src/main/java/org/hibernate/cache/jbc/access/PutFromLoadValidator.java	2009-10-08 19:43:00 UTC (rev 17658)
@@ -39,7 +39,6 @@
 
 import org.hibernate.cache.CacheException;
 
-
 /**
  * Encapsulates logic to allow a {@link TransactionalAccessDelegate} to determine
  * whether a {@link TransactionalAccessDelegate#putFromLoad(Object, Object, long, Object, boolean)
@@ -52,443 +51,450 @@
  * 
  * @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 various 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 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;
 
-   // -----------------------------------------------------------------  Public
-   
-   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;
-   }
+	/** Period after which a pending put is placed in the over-age queue */
+	private static final long PENDING_PUT_OVERAGE_PERIOD = 5 * 1000;
 
-   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);
-            }
-         }
-      }
-   }
+	/** Period before which we stop trying to clean out pending puts */
+	private static final long PENDING_PUT_RECENT_PERIOD = 2 * 1000;
 
-   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();
-      }
-   }
+	/**
+	 * 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;
 
-   /**
-    * 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);
-   }
-   // --------------------------------------------------------------  Protected
-   
-   /** Only for use by unit tests; may be removed at any time */
-   protected int getPendingPutQueueLength() {
-	   pendingLock.lock();
-	   try {
-		   return pendingQueue.size();
-	   }
-	   finally {
-		   pendingLock.unlock();
-	   }
-   }
-   
-   /** Only for use by unit tests; may be removed at any time */
-   protected int getOveragePendingPutQueueLength() {
-	   pendingLock.lock();
-	   try {
-		   return overagePendingQueue.size();
-	   }
-	   finally {
-		   pendingLock.unlock();
-	   }
-   }
-   
-   /** Only for use by unit tests; may be removed at any time */
-   protected int getRemovalQueueLength() {
-	   removalsLock.lock();
-	   try {
-		   return removalsQueue.size();
-	   }
-	   finally {
-		   removalsLock.unlock();
-	   }
-   }
-   // ----------------------------------------------------------------  Private
+	/**
+	 * Used to determine whether the owner of a pending put is a thread or a
+	 * transaction
+	 */
+	private final TransactionManager transactionManager;
 
-   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 final long nakedPutInvalidationPeriod;
+	private final long pendingPutOveragePeriod;
+	private final long pendingPutRecentPeriod;
+	private final long maxPendingPutDelay;
 
-   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(pos);
-            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) {
-                  overagePendingQueue.remove(0);
-                  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);
-         }        
-      }
+	/**
+	 * 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();
 
-      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;
-      }
+	/**
+	 * 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;
 
-      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);
-      }
-   }
-   
+	/**
+	 * 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 various
+	 * 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
+
+	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);
+	}
+
+	// -------------------------------------------------------------- Protected
+
+	/** Only for use by unit tests; may be removed at any time */
+	protected int getPendingPutQueueLength() {
+		pendingLock.lock();
+		try {
+			return pendingQueue.size();
+		} finally {
+			pendingLock.unlock();
+		}
+	}
+
+	/** Only for use by unit tests; may be removed at any time */
+	protected int getOveragePendingPutQueueLength() {
+		pendingLock.lock();
+		try {
+			return overagePendingQueue.size();
+		} finally {
+			pendingLock.unlock();
+		}
+	}
+
+	/** Only for use by unit tests; may be removed at any time */
+	protected int getRemovalQueueLength() {
+		removalsLock.lock();
+		try {
+			return removalsQueue.size();
+		} finally {
+			removalsLock.unlock();
+		}
+	}
+
+	// ---------------------------------------------------------------- Private
+
+	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(pos);
+				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) {
+						overagePendingQueue.remove(0);
+						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);
+		}
+	}
+
 }



More information about the hibernate-commits mailing list