All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.expiration.impl.ClusterExpirationManager Maven / Gradle / Ivy

There is a newer version: 9.1.7.Final
Show newest version
package org.infinispan.expiration.impl;

import static org.infinispan.commons.util.Util.toStr;

import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.infinispan.AdvancedCache;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.util.Util;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.container.entries.ExpiryHelper;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.factories.KnownComponentNames;
import org.infinispan.factories.annotations.ComponentName;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.marshall.core.MarshalledEntry;
import org.infinispan.metadata.InternalMetadata;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

import net.jcip.annotations.ThreadSafe;

/**
 * Allows for cluster based expirations to occur.  This provides guarantees that when an entry is expired that it will
 * expire that entry across the entire cluster at once.  This requires obtaining the lock for said entry before
 * expiration is performed.  Since expiration can occur without holding onto the lock it is possible for an expiration
 * to occur immediately after a value has been updated.  This can cause a premature expiration to occur.  Attempts
 * are made to prevent this by using the expired entry's value and lifespan to limit this expiration so it only happens
 * in a smaller amount of cases.
 * 

* Cache stores however do not supply the value or metadata information which means if an entry is purged from the cache * store that it will forcibly remove the value even if a concurrent write updated it just before. This will be * addressed by future SPI changes to the cache store. * @param * @param */ @ThreadSafe public class ClusterExpirationManager extends ExpirationManagerImpl { protected static final Log log = LogFactory.getLog(ClusterExpirationManager.class); protected static final boolean trace = log.isTraceEnabled(); private ExecutorService asyncExecutor; private AdvancedCache cache; private boolean needTransaction; @Inject public void inject(AdvancedCache cache, Configuration configuration, @ComponentName(KnownComponentNames.ASYNC_OPERATIONS_EXECUTOR) ExecutorService asyncExecutor) { this.cache = cache; this.asyncExecutor = asyncExecutor; needTransaction = configuration.transaction().transactionMode().isTransactional(); } @Override public void processExpiration() { long start = 0; if (!Thread.currentThread().isInterrupted()) { try { if (trace) { log.trace("Purging data container of expired entries"); start = timeService.time(); } long currentTimeMillis = timeService.wallClockTime(); for (Iterator> purgeCandidates = dataContainer.iteratorIncludingExpired(); purgeCandidates.hasNext();) { InternalCacheEntry e = purgeCandidates.next(); if (e.canExpire()) { if (ExpiryHelper.isExpiredMortal(e.getLifespan(), e.getCreated(), currentTimeMillis)) { handleLifespanExpireEntry(e, true); } else if (ExpiryHelper.isExpiredTransient(e.getMaxIdle(), e.getLastUsed(), currentTimeMillis)) { super.handleInMemoryExpiration(e, currentTimeMillis); } } } if (trace) { log.tracef("Purging data container completed in %s", Util.prettyPrintTime(timeService.timeDuration(start, TimeUnit.MILLISECONDS))); } } catch (Exception e) { log.exceptionPurgingDataContainer(e); } } if (!Thread.currentThread().isInterrupted()) { persistenceManager.purgeExpired(); } } void handleLifespanExpireEntry(InternalCacheEntry entry, boolean sync) { K key = entry.getKey(); // The most used case will be a miss so no extra read before if (expiring.putIfAbsent(key, key) == null) { long lifespan = entry.getLifespan(); if (trace) { log.tracef("Submitting expiration removal for key %s which had lifespan of %s", toStr(key), lifespan); } Runnable runnable = () -> { try { removeExpired(key, entry.getValue(), lifespan); } finally { expiring.remove(key); } }; if (sync) { runnable.run(); } else { asyncExecutor.submit(runnable); } } } private void removeExpired(K key, V value, Long lifespan) { if (needTransaction) { TransactionManager tm = cache.getTransactionManager(); try { Transaction tx = tm.suspend(); try { tm.begin(); cache.removeExpired(key, value, lifespan); } catch (NotSupportedException | SystemException e) { tm.rollback(); throw e; } finally { tm.commit(); } if (tx != null) { tm.resume(tx); } } catch (RollbackException | NotSupportedException | SystemException | HeuristicMixedException | HeuristicRollbackException | InvalidTransactionException e) { throw new CacheException(e); } } else { cache.removeExpired(key, value, lifespan); } } @Override public void handleInMemoryExpiration(InternalCacheEntry entry, long currentTime) { // We need to synchronize on the entry since {@link InternalCacheEntry} locks the entry when doing an update // so we can see both the new value and the metadata synchronized (entry) { if (ExpiryHelper.isExpiredMortal(entry.getLifespan(), entry.getCreated(), currentTime)) { handleLifespanExpireEntry(entry, false); } else { super.handleInMemoryExpiration(entry, currentTime); } } } @Override public void handleInStoreExpiration(K key) { if (expiring.putIfAbsent(key, key) == null) { // Unfortunately stores don't pull the entry so we can't tell exactly why it expired and thus we have to remove // the entire value. Unfortunately this could cause a concurrent write to be undone try { removeExpired(key, null, null); } finally { expiring.remove(key); } } } @Override public void handleInStoreExpiration(MarshalledEntry marshalledEntry) { K key = marshalledEntry.getKey(); if (expiring.putIfAbsent(key, key) == null) { try { InternalMetadata metadata = marshalledEntry.getMetadata(); removeExpired(key, marshalledEntry.getValue(), metadata.lifespan() == -1 ? null : metadata.lifespan()); } finally { expiring.remove(key); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy