com.gemstone.gemfire.internal.cache.ExpiryTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal.cache;
/**
* ExpiryTask represents a timeout event for expiration
*/
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import com.gemstone.gemfire.CancelException;
import com.gemstone.gemfire.InternalGemFireError;
import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.cache.CacheException;
import com.gemstone.gemfire.cache.CacheWriterException;
import com.gemstone.gemfire.cache.EntryNotFoundException;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.RegionDestroyedException;
import com.gemstone.gemfire.cache.util.BridgeWriterException;
import com.gemstone.gemfire.distributed.internal.PooledExecutorWithDMStats;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.LogWriterImpl;
import com.gemstone.gemfire.internal.SystemTimer;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.tcp.ConnectionTable;
public abstract class ExpiryTask extends SystemTimer.SystemTimerTask {
private LocalRegion region; // no longer final so cancel can null it out see bug 37574
private static final ThreadPoolExecutor executor;
static {
// default to inline expiry to fix bug 37115
int nThreads = Integer.getInteger("gemfire.EXPIRY_THREADS", 0).intValue();
if (nThreads > 0) {
ThreadFactory tf = new ThreadFactory() {
private int nextId = 0;
public Thread newThread(final Runnable command) {
String name = "Expiration threads";
final ThreadGroup group =
LogWriterImpl.createThreadGroup(name, (LogWriterI18n)null);
final Runnable r = new Runnable() {
public void run() {
ConnectionTable.threadWantsSharedResources();
try {
command.run();
} finally {
ConnectionTable.releaseThreadsSockets();
}
}
};
Thread thread = new Thread(group, r, "Expiry " + nextId++);
thread.setDaemon(true);
return thread;
}
};
//LinkedBlockingQueue q = new LinkedBlockingQueue();
SynchronousQueue q = new SynchronousQueue();
executor = new PooledExecutorWithDMStats(q, nThreads, tf);
} else {
executor = null;
}
}
protected ExpiryTask(LocalRegion region) {
this.region = region;
}
protected abstract ExpirationAttributes getIdleAttributes();
protected abstract ExpirationAttributes getTTLAttributes();
/**
* @return the absolute time (ms since Jan 1, 1970) at which this
* region expires, due to either time-to-live or idle-timeout (whichever
* will occur first), or 0 if neither are used.
*/
long getExpirationTime() throws EntryNotFoundException {
long ttl = getTTLExpirationTime();
long idle = getIdleExpirationTime();
if (ttl == 0) {
return idle;
} else if (idle == 0) {
return ttl;
}
return Math.min(ttl, idle);
}
/** Return the absolute time when TTL expiration occurs, or 0 if not used */
protected final long getTTLExpirationTime() throws EntryNotFoundException {
long ttl = getTTLAttributes().getTimeout();
long tilt = 0;
if (ttl > 0) {
if (!getLocalRegion().EXPIRY_UNITS_MS) {
ttl *= 1000;
}
tilt = getLastModifiedTime() + ttl;
}
return tilt;
}
/** Return the absolute time when idle expiration occurs, or 0 if not used */
protected final long getIdleExpirationTime() throws EntryNotFoundException {
long idle = getIdleAttributes().getTimeout();
long tilt = 0;
if (idle > 0) {
if (!getLocalRegion().EXPIRY_UNITS_MS) {
idle *= 1000;
}
tilt = getLastAccessedTime() + idle;
}
return tilt;
}
/** Returns the number of milliseconds until this task should expire.
The return value will never be negative. */
final long getExpiryMillis() throws EntryNotFoundException {
long extm = getExpirationTime() - getNow();
if (extm < 0L)
return 0L;
else
return extm;
}
/**
* Return true if current task could have expired.
* Return false if expiration is impossible.
*/
protected boolean isExpirationPossible() throws EntryNotFoundException {
long expTime = getExpirationTime();
if (expTime > 0L && getNow() >= expTime) {
return true;
}
return false;
}
/**
* Returns false if the region reliability state does not allow this expiry
* task to fire.
*/
protected boolean isExpirationAllowed() {
return getLocalRegion().isExpirationAllowed(this);
}
protected void performTimeout() throws CacheException {
if (getLoggerI18n().fineEnabled()) {
getLoggerI18n().fine(this.toString() + ".performTimeout(): getExpirationTime() returns " + getExpirationTime());
}
getLocalRegion().performExpiryTimeout(this);
}
/**
* Called by LocalRegion#basicProcessPendingExpires and LocalRegion#handleInitialPendingExpires
*/
protected void basicPerformTimeout(boolean isPending) throws CacheException {
if (!isExpirationAllowed()) {
return;
}
if (isExpirationPossible()) {
expire(isPending);
}
reschedule();
}
/**
* @guarded.By suspendLock
*/
private static boolean expirationSuspended = false;
private static final Object suspendLock = new Object();
/**
* Test method that causes expiration to be suspended until
* permitExpiration is called.
* @since 5.0
*/
public final static void suspendExpiration() {
synchronized (suspendLock) {
expirationSuspended = true;
}
}
public final static void permitExpiration() {
synchronized (suspendLock) {
expirationSuspended = false;
suspendLock.notifyAll();
}
}
/**
* Wait until permission is given for expiration to be done.
* Tests are allowed to suspend expiration.
* @since 5.0
*/
private final void waitOnExpirationSuspension() {
for (;;) {
getLocalRegion().getCancelCriterion().checkCancelInProgress(null);
synchronized (suspendLock) {
boolean interrupted = Thread.interrupted();
try {
while (expirationSuspended) {
suspendLock.wait();
}
break;
} catch (InterruptedException ex) {
interrupted = true;
getLocalRegion().getCancelCriterion().checkCancelInProgress(null);
// keep going, we can't cancel
}
finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
} // synchronized
} // for
}
protected final boolean expire(boolean isPending) throws CacheException
{
waitOnExpirationSuspension();
ExpirationAction action = getAction();
if (action == null) return false;
return expire(action, isPending);
}
/** Why did this expire?
* @return the action to perform or null if NONE */
protected ExpirationAction getAction() {
long ttl = getTTLExpirationTime();
long idle = getIdleExpirationTime();
if (ttl == 0) {
if (idle == 0) return null;
return getIdleAttributes().getAction();
}
if (idle == 0) {
// we know ttl != 0
return getTTLAttributes().getAction();
}
// Neither is 0
if (idle < ttl) {
return getIdleAttributes().getAction();
}
return getTTLAttributes().getAction();
}
/** Returns true if the ExpirationAction is a distributed action. */
protected boolean isDistributedAction() {
ExpirationAction action = getAction();
return action != null && (action.isInvalidate() || action.isDestroy());
}
final LocalRegion getLocalRegion() {
return this.region;
}
protected final boolean expire(ExpirationAction action, boolean isPending) throws CacheException {
if (action.isInvalidate()) return invalidate();
if (action.isDestroy()) return destroy(isPending);
if (action.isLocalInvalidate()) return localInvalidate();
if (action.isLocalDestroy()) return localDestroy();
throw new InternalGemFireError(LocalizedStrings.ExpiryTask_UNRECOGNIZED_EXPIRATION_ACTION_0.toLocalizedString(action));
}
/**
* Cancel this task
*/
@Override
public boolean cancel() {
boolean superCancel = super.cancel();
LocalRegion lr = getLocalRegion();
if (lr != null) {
lr.cancelPendingExpiry(this); // always call or only if superCancel?
if (superCancel) {
this.region = null; // this is the only place it is nulled
}
}
return superCancel;
}
/**
* An ExpiryTask is sent run() to perform its task. Note that
* this run() method should never throw an exception - otherwise,
* it takes out the java.util.Timer thread, causing an exception
* whenever we try to schedule more expiration tasks.
*/
@Override
public final void run2() {
try {
if (executor != null) {
executor.execute(new Runnable() {
public void run() {
runInThreadPool();
}
});
} else {
// inline
runInThreadPool();
}
} catch (RejectedExecutionException ex) {
getLoggerI18n().fine("Rejected execution in expiration task", ex);
} catch (CancelException e) {
return; // just bail
} catch (VirtualMachineError err) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
} catch (Throwable ex) {
// Whenever you catch Error or Throwable, you must also
// check for fatal JVM error (see above). However, there is
// _still_ a possibility that you are dealing with a cascading
// error condition, so you also need to check to see if the JVM
// is still usable:
SystemFailure.checkFailure();
getLoggerI18n().severe(LocalizedStrings.ExpiryTask_EXCEPTION_IN_EXPIRATION_TASK, ex);
}
}
protected void runInThreadPool() {
try {
if (isCacheClosing() ||
getLocalRegion().isClosed() ||
getLocalRegion().isDestroyed()) {
return;
}
if (getLoggerI18n().finerEnabled()) {
getLoggerI18n().finer(this.toString() + "is fired");
}
// do our work...
performTimeout();
} catch (RegionDestroyedException re) {
// Ignore - our job is done
} catch (EntryNotFoundException ex) {
// Ignore
} catch (CancelException ex) {
// ignore
// @todo grid: do we need to deal with pool exceptions here?
} catch (BridgeWriterException ex) {
// Some exceptions from the bridge writer should not be logged.
Throwable cause = ex.getCause();
// BridgeWriterExceptions from the server are wrapped in CacheWriterExceptions
if (cause != null && cause instanceof CacheWriterException) {
cause = cause.getCause();
}
if (cause instanceof RegionDestroyedException ||
cause instanceof EntryNotFoundException ||
cause instanceof CancelException) {
// ignore
LogWriterI18n logger = getLoggerI18n();
if (logger.fineEnabled()) {
logger.fine("Exception in expiration task", ex);
}
} else {
getLoggerI18n().severe(LocalizedStrings.ExpiryTask_EXCEPTION_IN_EXPIRATION_TASK, ex);
}
} catch (VirtualMachineError err) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
} catch (Throwable ex) {
// Whenever you catch Error or Throwable, you must also
// check for fatal JVM error (see above). However, there is
// _still_ a possibility that you are dealing with a cascading
// error condition, so you also need to check to see if the JVM
// is still usable:
SystemFailure.checkFailure();
getLoggerI18n().severe(LocalizedStrings.ExpiryTask_EXCEPTION_IN_EXPIRATION_TASK, ex);
}
}
protected boolean isCacheClosing() {
return ((GemFireCacheImpl) getLocalRegion().getCache()).isClosed();
}
/**
* Reschedule (or not) this task for later consideration
*/
abstract protected void reschedule() throws CacheException;
@Override
public LogWriterI18n getLoggerI18n() {
return getLocalRegion().getCache().getLoggerI18n();
}
@Override
public String toString() {
String expTtl = "";
String expIdle = "";
try {
if (getTTLAttributes() != null) {
expTtl = String.valueOf(getTTLExpirationTime());
}
if (getIdleAttributes() != null) {
expIdle = String.valueOf(getIdleExpirationTime());
}
}
catch (VirtualMachineError err) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
}
catch (Throwable t) {
// Whenever you catch Error or Throwable, you must also
// catch VirtualMachineError (see above). However, there is
// _still_ a possibility that you are dealing with a cascading
// error condition, so you also need to check to see if the JVM
// is still usable:
SystemFailure.checkFailure();
}
return super.toString() + " for " + getLocalRegion()
+ ", ttl expiration time: " + expTtl
+ ", idle expiration time: " + expIdle +
("[now:" + System.currentTimeMillis() + "]");
}
////// Abstract methods ///////
protected abstract long getLastModifiedTime() throws EntryNotFoundException;
protected abstract long getLastAccessedTime() throws EntryNotFoundException;
protected abstract boolean invalidate() throws CacheException;
protected abstract boolean destroy(boolean isPending) throws CacheException;
protected abstract boolean localInvalidate() throws EntryNotFoundException;
protected abstract boolean localDestroy() throws CacheException;
protected abstract void addExpiryTask() throws EntryNotFoundException;
public abstract boolean isPending();
public abstract Object getKey();
private static final ThreadLocal now = new ThreadLocal();
/**
* To reduce the number of times we need to call System.currentTimeMillis you
* can call this method to set a thread local. Make sure and call
* {@link #clearNow()} in a finally block after calling this method.
*/
public static void setNow() {
GemFireCacheImpl cache = GemFireCacheImpl.getInstance();
if (cache != null) {
// Do not use cache.cacheTimeMillis here.
// Since expiration uses the System timer we need to use its clock.
now.set(System.currentTimeMillis());
}
}
/**
* Call this method after a thread has called {@link #setNow()} once you are
* done calling code that may call {@link #getNow()}.
*/
public static void clearNow() {
now.remove();
}
/**
* Returns the current time in milliseconds. If the current thread has called
* {@link #setNow()} then that time is return.
*
* @return the current time in milliseconds
*/
public static long getNow() {
long result;
Long tl = now.get();
if (tl != null) {
result = tl.longValue();
} else {
// Do not use cache.cacheTimeMillis here.
// Since expiration uses the System timer we need to use its clock.
result = System.currentTimeMillis();
}
return result;
}
}