blob: ca9a4ef83518ee463022aa0407d758da2c5b1978 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2002-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.internal.cache;
/**
* EntryExpiryTask represents a timeout event for a region entry.
*/
import java.util.concurrent.locks.Lock;
import org.apache.logging.log4j.Logger;
import com.gemstone.gemfire.cache.CacheException;
import com.gemstone.gemfire.cache.EntryDestroyedException;
import com.gemstone.gemfire.cache.EntryNotFoundException;
import com.gemstone.gemfire.cache.ExpirationAction;
import com.gemstone.gemfire.cache.ExpirationAttributes;
import com.gemstone.gemfire.cache.Operation;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.TimeoutException;
import com.gemstone.gemfire.internal.InternalStatisticsDisabledException;
import com.gemstone.gemfire.internal.logging.LogService;
public class EntryExpiryTask extends ExpiryTask {
private static final Logger logger = LogService.getLogger();
/**
* The region entry we are working with
*/
private RegionEntry re; // not final so cancel can null it out see bug 37574
/*
* This was added to accommodate a session replication requirement where an
* empty client has a need to access the expired entry so that additional
* processing can be performed on it.
*
* This field is nether private nor final so that dunits can manipulate it as
* necessary.
*/
public static boolean expireSendsEntryAsCallback =
Boolean.getBoolean("gemfire.EXPIRE_SENDS_ENTRY_AS_CALLBACK");
protected EntryExpiryTask(LocalRegion region, RegionEntry re) {
super(region);
this.re = re;
}
@Override
protected ExpirationAttributes getTTLAttributes() {
return getLocalRegion().getAttributes().getEntryTimeToLive();
}
@Override
protected ExpirationAttributes getIdleAttributes() {
return getLocalRegion().getAttributes().getEntryIdleTimeout();
}
protected RegionEntry getRegionEntry() {
return this.re;
}
/**
* Returns the tasks region entry if it "checks" out. The check is to
* see if the region entry still exists.
* @throws EntryNotFoundException if the task no longer has a region entry or
* if the region entry it has is removed.
*/
protected RegionEntry getCheckedRegionEntry() throws EntryNotFoundException {
RegionEntry result = this.re;
if (re == null || re.isDestroyedOrRemoved()) {
throw new EntryNotFoundException("expiration task no longer has access to region entry");
}
return result;
}
@Override
protected long getLastAccessedTime() throws EntryNotFoundException
{
RegionEntry re = getCheckedRegionEntry();
try {
return re.getLastAccessed();
}
catch (InternalStatisticsDisabledException e) {
return 0;
}
}
@Override
protected long getLastModifiedTime() throws EntryNotFoundException
{
return getCheckedRegionEntry().getLastModified();
}
private Object getValueForCallback(LocalRegion r, Object k) {
Region.Entry<?,?> e = r.getEntry(k);
return (e != null) ? e.getValue() : null;
}
private Object createExpireEntryCallback(LocalRegion r, Object k) {
return expireSendsEntryAsCallback ? getValueForCallback(r, k) : null;
}
@Override
protected boolean destroy(boolean isPending) throws CacheException
{
RegionEntry re = getCheckedRegionEntry();
Object key = re.getKey();
LocalRegion lr = getLocalRegion();
EntryEventImpl event = EntryEventImpl.create(
lr, Operation.EXPIRE_DESTROY, key, null,
createExpireEntryCallback(lr, key), false, lr.getMyId());
try {
event.setPendingSecondaryExpireDestroy(isPending);
if (lr.generateEventID()) {
event.setNewEventId(lr.getCache().getDistributedSystem());
}
lr.expireDestroy(event, true); // expectedOldValue
return true;
} finally {
event.release();
}
}
@Override
protected boolean invalidate() throws TimeoutException,
EntryNotFoundException
{
RegionEntry re = getCheckedRegionEntry();
Object key = re.getKey();
LocalRegion lr = getLocalRegion();
EntryEventImpl event = EntryEventImpl.create(lr,
Operation.EXPIRE_INVALIDATE, key, null,
createExpireEntryCallback(lr, key), false, lr.getMyId());
try {
if (lr.generateEventID()) {
event.setNewEventId(lr.getCache().getDistributedSystem());
}
lr.expireInvalidate(event);
} finally {
event.release();
}
return true;
}
@Override
protected boolean localDestroy() throws CacheException
{
RegionEntry re = getCheckedRegionEntry();
Object key = re.getKey();
LocalRegion lr = getLocalRegion();
EntryEventImpl event = EntryEventImpl.create(lr,
Operation.EXPIRE_LOCAL_DESTROY, key, null,
createExpireEntryCallback(lr, key), false, lr.getMyId());
try {
if (lr.generateEventID()) {
event.setNewEventId(lr.getCache().getDistributedSystem());
}
lr.expireDestroy(event, false); // expectedOldValue
} finally {
event.release();
}
return true;
}
@Override
protected boolean localInvalidate() throws EntryNotFoundException
{
RegionEntry re = getCheckedRegionEntry();
Object key = re.getKey();
LocalRegion lr = getLocalRegion();
EntryEventImpl event = EntryEventImpl.create(lr,
Operation.EXPIRE_LOCAL_INVALIDATE, key, null,
createExpireEntryCallback(lr, key), false, lr.getMyId());
try {
if (lr.generateEventID()) {
event.setNewEventId(lr.getCache().getDistributedSystem());
}
lr.expireInvalidate(event);
} finally {
event.release();
}
return true;
}
@Override
final protected void reschedule() throws CacheException
{
if (isCacheClosing() || getLocalRegion().isClosed() || getLocalRegion().isDestroyed()
|| !isExpirationAllowed()) {
return;
}
if (getExpirationTime() > 0) {
addExpiryTask();
}
}
@Override
protected void addExpiryTask() throws EntryNotFoundException
{
getLocalRegion().addExpiryTask(getCheckedRegionEntry());
}
@Override
public String toString()
{
String result = super.toString();
RegionEntry re = this.re;
if (re != null) {
result += ", " + re.getKey();
}
return result;
}
@Override
protected void performTimeout() throws CacheException
{
// remove the task from the region's map first thing
// so the next call to addExpiryTaskIfAbsent will
// add a new task instead of doing nothing, which would
// erroneously cancel expiration for this key.
getLocalRegion().cancelExpiryTask(this.re);
getLocalRegion().performExpiryTimeout(this);
}
@Override
public boolean isPending() {
RegionEntry re = this.re;
if(re == null) {
return false;
}
if (re.isDestroyedOrRemoved()) {
return false;
}
ExpirationAction action = getAction();
if (action == null) {
return false;
}
if((action.isInvalidate() || action.isLocalInvalidate()) && re.isInvalid()) {
return false;
}
return true;
}
@Override
protected ExpirationAction getAction() {
long ttl = getTTLAttributes().getTimeout();
long idle = getIdleAttributes().getTimeout();
ExpirationAction action;
if (ttl == 0) {
action = getIdleAttributes().getAction();
}
else
if (idle != 0 && idle < ttl) {
action = getIdleAttributes().getAction();
}
else {
action = getTTLAttributes().getAction();
}
return action;
}
/**
* Called by LocalRegion#performExpiryTimeout
*/
@Override
protected void basicPerformTimeout(boolean isPending) throws CacheException
{
if (!isExpirationAllowed()) {
return;
}
if (!isExpirationPossible()) {
reschedule();
return;
}
// Need to figure out why it expired - ttl, or idle timeout?
ExpirationAction action;
long ttl = getTTLAttributes().getTimeout();
long idle = getIdleAttributes().getTimeout();
if (ttl == 0) {
action = getIdleAttributes().getAction();
}
else
if (idle != 0 && idle < ttl) {
action = getIdleAttributes().getAction();
}
else {
action = getTTLAttributes().getAction();
}
// if global scope get distributed lock for destroy and invalidate actions
if (getLocalRegion().getScope().isGlobal()
&& (action.isDestroy() || action.isInvalidate())) {
Lock lock = getLocalRegion().getDistributedLock(getCheckedRegionEntry().getKey());
lock.lock();
try {
long expTime = getExpirationTime();
if (expTime == 0L) {
return;
}
if (getNow() >= expTime) {
if (logger.isTraceEnabled()) {
// NOTE: original finer message used this.toString() twice
logger.trace("{}.performTimeout().getExpirationTime() is {}; {}.expire({}). ttlExpiration: {}, idleExpiration: {}, ttlAttrs: {}, idleAttrs: {} action is: {}",
this, expTime, this, action, ttl, idle, getTTLAttributes(), getIdleAttributes());
}
expire(action, isPending);
return;
}
}
finally {
lock.unlock();
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("{}..performTimeout().getExpirationTime() is {}", this, getExpirationTime());
}
expire(isPending);
return;
}
reschedule();
}
@Override
public Object getKey() {
RegionEntry entry = this.re;
if (entry == null) {
throw new EntryDestroyedException();
}
return entry.getKey();
}
@Override
public boolean cancel() {
boolean superCancel = super.cancel();
if (superCancel) {
this.re = null;
}
return superCancel;
}
}