/** Merge the transactional caches into the shared cache */ public void beforeCommit(boolean readOnly) { if (isDebugEnabled) { logger.debug("Processing before-commit"); } TransactionData txnData = getTransactionData(); try { if (txnData.isClearOn) { // clear shared cache final long startNanos = cacheStatsEnabled ? System.nanoTime() : 0; sharedCache.clear(); final long endNanos = cacheStatsEnabled ? System.nanoTime() : 0; if (cacheStatsEnabled) { TransactionStats stats = txnData.stats; stats.record(startNanos, endNanos, OpType.CLEAR); } if (isDebugEnabled) { logger.debug("Clear notification recieved in commit - clearing shared cache"); } } else { // transfer any removed items for (Serializable key : txnData.removedItemsCache) { final long startNanos = System.nanoTime(); sharedCache.remove(key); final long endNanos = System.nanoTime(); TransactionStats stats = txnData.stats; stats.record(startNanos, endNanos, OpType.REMOVE); } if (isDebugEnabled) { logger.debug( "Removed " + txnData.removedItemsCache.size() + " values from shared cache in commit"); } } // transfer updates Set<Serializable> keys = (Set<Serializable>) txnData.updatedItemsCache.keySet(); for (Map.Entry<Serializable, CacheBucket<V>> entry : (Set<Map.Entry<Serializable, CacheBucket<V>>>) txnData.updatedItemsCache.entrySet()) { Serializable key = entry.getKey(); CacheBucket<V> bucket = entry.getValue(); bucket.doPreCommit( sharedCache, key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly); } if (isDebugEnabled) { logger.debug("Pre-commit called for " + keys.size() + " values."); } } catch (Throwable e) { throw new AlfrescoRuntimeException("Failed to transfer updates to shared cache", e); } finally { // Block any further updates txnData.isClosed = true; } }
/** * Transaction-long setting to force all the share cache to be bypassed for the current * transaction. * * <p>This setting is like having a {@link NullCache null} {@link #setSharedCache(SimpleCache) * shared cache}, but only lasts for the transaction. * * <p>Use this when a read transaction <b>must</b> see consistent and current data i.e. go to the * database. While this is active, write operations will also not be committed to the shared * cache. * * @param noSharedCacheRead <tt>true</tt> to avoid reading from the shared cache for the * transaction */ @SuppressWarnings("unchecked") public void setDisableSharedCacheReadForTransaction(boolean noSharedCacheRead) { TransactionData txnData = getTransactionData(); // If we are switching on noSharedCacheRead mode, convert all existing reads and updates to // avoid 'consistent // read' behaviour giving us a potentially out of date node already accessed if (noSharedCacheRead && !txnData.noSharedCacheRead) { txnData.noSharedCacheRead = noSharedCacheRead; String currentCacheRegion = TenantUtil.getCurrentDomain(); for (Map.Entry<Serializable, CacheBucket<V>> entry : new ArrayList<Map.Entry<Serializable, CacheBucket<V>>>( txnData.updatedItemsCache.entrySet())) { Serializable cacheKey = entry.getKey(); K key = null; if (cacheKey instanceof CacheRegionKey) { CacheRegionKey cacheRegionKey = (CacheRegionKey) cacheKey; if (currentCacheRegion.equals(cacheRegionKey.getCacheRegion())) { key = (K) cacheRegionKey.getCacheKey(); } } else { key = (K) cacheKey; } if (key != null) { CacheBucket<V> bucket = entry.getValue(); // Simply 'forget' reads if (bucket instanceof ReadCacheBucket) { txnData.updatedItemsCache.remove(cacheKey); } // Convert updates to removes else if (bucket instanceof UpdateCacheBucket) { remove(key); } // Leave new entries alone - they can't have come from the shared cache } } } }
/** Clears out all the caches. */ public void clear() { // clear local caches if (AlfrescoTransactionSupport.getTransactionId() != null) { if (isDebugEnabled) { logger.debug( "In transaction clearing cache: \n" + " cache: " + this + "\n" + " txn: " + AlfrescoTransactionSupport.getTransactionId()); } TransactionData txnData = getTransactionData(); // Ensure that the cache isn't being modified if (txnData.isClosed) { if (isDebugEnabled) { logger.debug("In post-commit clear: \n" + " cache: " + this); } } else { // The shared cache must be cleared at the end of the transaction // and also serves to ensure that the shared cache will be ignored // for the remainder of the transaction. // We do, however, keep all locked values locked. txnData.isClearOn = true; txnData.updatedItemsCache.clear(); txnData.removedItemsCache.clear(); } } else // no transaction { if (isDebugEnabled) { logger.debug("No transaction - clearing shared cache"); } // clear shared cache sharedCache.clear(); } }
/** To be used in a transaction only. */ private TransactionData getTransactionData() { @SuppressWarnings("unchecked") TransactionData data = (TransactionData) AlfrescoTransactionSupport.getResource(resourceKeyTxnData); if (data == null) { data = new TransactionData(); // create and initialize caches data.updatedItemsCache = new LRULinkedHashMap<Serializable, CacheBucket<V>>(23); data.removedItemsCache = new HashSet<Serializable>(13); data.lockedItemsCache = new HashSet<Serializable>(13); data.isReadOnly = AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; data.stats = new TransactionStats(); // ensure that we get the transaction callbacks as we have bound the unique // transactional caches to a common manager AlfrescoTransactionSupport.bindListener(this); AlfrescoTransactionSupport.bindResource(resourceKeyTxnData, data); } return data; }
/** * Goes direct to the shared cache in the absence of a transaction. * * <p>Where a transaction is present, a cache of removed items is lazily added to the thread and * the <tt>Object</tt> put onto that. */ public void remove(K keyIn) { final Serializable key = getTenantAwareCacheKey(keyIn); // are we in a transaction? if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction { // no transaction sharedCache.remove(key); // done if (isDebugEnabled) { logger.debug( "No transaction - removing item from shared cache: \n" + " cache: " + this + "\n" + " key: " + key); } } else // transaction present { TransactionData txnData = getTransactionData(); // Ensure that the cache isn't being modified if (txnData.isClosed) { if (isDebugEnabled) { logger.debug("In post-commit remove: \n" + " cache: " + this + "\n" + " key: " + key); } } else if (isValueLocked(txnData, key)) { // The key has been locked if (isDebugEnabled) { logger.debug( "Ignoring remove after detecting locked key: \n" + " cache: " + this + "\n" + " key: " + key); } } else { // is the shared cache going to be cleared? if (txnData.isClearOn) { // don't store removals if we're just going to clear it all out later } else { // are we in an overflow condition? if (txnData.removedItemsCache.size() >= maxCacheSize) { // overflow about to occur or has occured - we can only guarantee non-stale // data by clearing the shared cache after the transaction. Also, the // shared cache needs to be ignored for the rest of the transaction. txnData.isClearOn = true; if (!txnData.haveIssuedFullWarning) { if (logger.isInfoEnabled()) { Exception e = new Exception("Stack: "); logger.info( "Transactional removal cache '" + name + "' is full (" + maxCacheSize + ").", e); } else if (logger.isWarnEnabled()) { logger.warn( "Transactional removal cache '" + name + "' is full (" + maxCacheSize + ")."); } txnData.haveIssuedFullWarning = true; } } else { // Create a bucket to remove the value from the shared cache txnData.removedItemsCache.add(key); } } // remove the item from the udpated cache, if present txnData.updatedItemsCache.remove(key); // done if (isDebugEnabled) { logger.debug( "In transaction - adding item direct to transactional removed cache: \n" + " cache: " + this + "\n" + " key: " + key); } } } }
/** * Goes direct to the shared cache in the absence of a transaction. * * <p>Where a transaction is present, a cache of updated items is lazily added to the thread and * the <tt>Object</tt> put onto that. */ public void put(K keyIn, V value) { final Serializable key = getTenantAwareCacheKey(keyIn); // are we in a transaction? if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction { // no transaction TransactionalCache.putSharedCacheValue(sharedCache, key, value, null); // done if (isDebugEnabled) { logger.debug( "No transaction - adding item direct to shared cache: \n" + " cache: " + this + "\n" + " key: " + key + "\n" + " value: " + value); } } else // transaction present { TransactionData txnData = getTransactionData(); // Ensure that the cache isn't being modified if (txnData.isClosed) { if (isDebugEnabled) { logger.debug( "In post-commit add: \n" + " cache: " + this + "\n" + " key: " + key + "\n" + " value: " + value); } } else if (isValueLocked(txnData, key)) { // The key has been locked if (isDebugEnabled) { logger.debug( "Ignoring put after detecting locked key: \n" + " cache: " + this + "\n" + " key: " + key + "\n" + " value: " + value); } } else { // we have an active transaction - add the item into the updated cache for this transaction // are we in an overflow condition? if (txnData.updatedItemsCache.hasHitSize()) { // overflow about to occur or has occured - we can only guarantee non-stale // data by clearing the shared cache after the transaction. Also, the // shared cache needs to be ignored for the rest of the transaction. txnData.isClearOn = true; if (!txnData.haveIssuedFullWarning) { if (logger.isInfoEnabled()) { Exception e = new Exception("Stack: "); logger.info( "Transactional update cache '" + name + "' is full (" + maxCacheSize + ").", e); } else if (logger.isWarnEnabled()) { logger.warn( "Transactional update cache '" + name + "' is full (" + maxCacheSize + ")."); } txnData.haveIssuedFullWarning = true; } } ValueHolder<V> existingValueHolder = txnData.noSharedCacheRead ? null : sharedCache.get(key); CacheBucket<V> bucket = null; if (existingValueHolder == null) { // ALF-5134: Performance of Alfresco cluster less than performance of single node // The 'null' marker that used to be inserted also triggered an update in the afterCommit // phase; the update triggered cache invalidation in the cluster. Now, the null cannot // be verified to be the same null - there is no null equivalence // // The value didn't exist before bucket = new NewCacheBucket<V>(value); } else { // Record the existing value as is bucket = new UpdateCacheBucket<V>(existingValueHolder, value); } txnData.updatedItemsCache.put(key, bucket); // remove the item from the removed cache, if present txnData.removedItemsCache.remove(key); // done if (isDebugEnabled) { logger.debug( "In transaction - adding item direct to transactional update cache: \n" + " cache: " + this + "\n" + " key: " + key + "\n" + " value: " + value); } } } }
/** * Ensures that the transactional caches are removed from the common cache manager. * * @param txnData the data with references to the the transactional caches */ private void removeCaches(TransactionData txnData) { txnData.isClosed = true; }