Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.gemstone.gemfire.internal.cache.BucketRegion Maven / Gradle / Ivy
/*
* 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.
*/
/*
* Changes for SnappyData distributed computational and data platform.
*
* Portions Copyright (c) 2018 SnappyData, 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;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.gemstone.gemfire.*;
import com.gemstone.gemfire.cache.*;
import com.gemstone.gemfire.cache.hdfs.HDFSIOException;
import com.gemstone.gemfire.cache.hdfs.internal.AbstractBucketRegionQueue;
import com.gemstone.gemfire.cache.hdfs.internal.hoplog.HoplogOrganizer;
import com.gemstone.gemfire.cache.partition.PartitionListener;
import com.gemstone.gemfire.cache.query.internal.IndexUpdater;
import com.gemstone.gemfire.distributed.DistributedMember;
import com.gemstone.gemfire.distributed.DistributedSystem;
import com.gemstone.gemfire.distributed.internal.AtomicLongWithTerminalState;
import com.gemstone.gemfire.distributed.internal.DirectReplyProcessor;
import com.gemstone.gemfire.distributed.internal.DistributionAdvisor.Profile;
import com.gemstone.gemfire.distributed.internal.DistributionManager;
import com.gemstone.gemfire.distributed.internal.DistributionStats;
import com.gemstone.gemfire.distributed.internal.MembershipListener;
import com.gemstone.gemfire.distributed.internal.membership.InternalDistributedMember;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.Assert;
import com.gemstone.gemfire.internal.HeapDataOutputStream;
import com.gemstone.gemfire.internal.cache.BucketAdvisor.BucketProfile;
import com.gemstone.gemfire.internal.cache.FilterRoutingInfo.FilterInfo;
import com.gemstone.gemfire.internal.cache.control.MemoryEvent;
import com.gemstone.gemfire.internal.cache.delta.Delta;
import com.gemstone.gemfire.internal.cache.locks.ExclusiveSharedLockObject;
import com.gemstone.gemfire.internal.cache.locks.LockMode;
import com.gemstone.gemfire.internal.cache.locks.LockingPolicy.ReadEntryUnderLock;
import com.gemstone.gemfire.internal.cache.locks.ReentrantReadWriteWriteShareLock;
import com.gemstone.gemfire.internal.cache.partitioned.*;
import com.gemstone.gemfire.internal.cache.tier.sockets.CacheClientNotifier;
import com.gemstone.gemfire.internal.cache.tier.sockets.ClientTombstoneMessage;
import com.gemstone.gemfire.internal.cache.tier.sockets.ClientUpdateMessage;
import com.gemstone.gemfire.internal.cache.versions.VersionSource;
import com.gemstone.gemfire.internal.cache.versions.VersionStamp;
import com.gemstone.gemfire.internal.cache.versions.VersionTag;
import com.gemstone.gemfire.internal.cache.wan.GatewaySenderEventImpl;
import com.gemstone.gemfire.internal.cache.wan.parallel.ConcurrentParallelGatewaySenderQueue;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.offheap.StoredObject;
import com.gemstone.gemfire.internal.offheap.annotations.Unretained;
import com.gemstone.gemfire.internal.shared.Version;
import com.gemstone.gemfire.internal.snappy.CallbackFactoryProvider;
import com.gemstone.gemfire.internal.snappy.StoreCallbacks;
/**
* The storage used for a Partitioned Region.
* This class asserts distributed scope as well as a replicate data policy
* It does not support transactions
*
* Primary election for a BucketRegion can be found in the
* {@link com.gemstone.gemfire.internal.cache.BucketAdvisor} class
*
* @author Mitch Thomas
* @since 5.1
*
*/
public class BucketRegion extends DistributedRegion implements Bucket {
/**
* A special value for the bucket size indicating that this bucket
* has been destroyed.
*/
private static final long BUCKET_DESTROYED = Long.MIN_VALUE;
private final AtomicLong counter = new AtomicLong();
private AtomicLong limit;
private final AtomicLong numOverflowOnDisk = new AtomicLong();
private final AtomicLong numOverflowBytesOnDisk = new AtomicLong();
private final AtomicLong numEntriesInVM = new AtomicLong();
private final AtomicLong evictions = new AtomicLong();
private static final ThreadLocal bucketRegionIndexCleaner =
new ThreadLocal() ;
/**
* Contains size in bytes of the values stored
* in theRealMap. Sizes are tallied during put and remove operations.
*/
private final AtomicLongWithTerminalState bytesInMemory =
new AtomicLongWithTerminalState();
private final AtomicLong inProgressSize = new AtomicLong();
public static final ReadEntryUnderLock READ_SER_VALUE = new ReadEntryUnderLock() {
public final Object readEntry(final ExclusiveSharedLockObject lockObj,
final Object context, final int iContext, boolean allowTombstones) {
final RegionEntry re = (RegionEntry)lockObj;
final BucketRegion br = (BucketRegion)context;
return br.getSerialized(re, (iContext & DO_NOT_LOCK_ENTRY) != 0);
}
};
public static class RawValue {
public static final RawValue NULLVALUE = GemFireCacheImpl.FactoryStatics
.rawValueFactory.newInstance(null);
public static final RawValue REQUIRES_ENTRY_LOCK = GemFireCacheImpl
.FactoryStatics.rawValueFactory.newInstance(null);
protected Object rawValue;
protected transient boolean fromCacheLoader;
protected RawValue(Object rawVal) {
this.rawValue = rawVal;
}
public static RawValue newInstance(final Object rawVal,
final GemFireCacheImpl cache) {
return cache.getRawValueFactory().newInstance(rawVal);
}
public final boolean isValueByteArray() {
return this.rawValue instanceof byte[];
}
public final Object getRawValue() {
return this.rawValue;
}
public final void writeAsByteArray(DataOutput out) throws IOException {
if (isValueByteArray()) {
DataSerializer.writeByteArray((byte[]) this.rawValue, out);
} else if (this.rawValue instanceof CachedDeserializable) {
((CachedDeserializable)this.rawValue).writeValueAsByteArray(out);
} else if (Token.isInvalid(this.rawValue)) {
DataSerializer.writeByteArray(null, out);
} else if (this.rawValue == Token.TOMBSTONE) {
DataSerializer.writeByteArray(null, out);
} else {
DataSerializer.writeObjectAsByteArray(this.rawValue, out);
}
}
/**
* Return the de-serialized value without changing the stored form
* in the heap. This causes local access to create a de-serialized copy (extra work)
* in favor of keeping values in serialized form which is important because
* it makes remote access more efficient. This assumption is that remote
* access is much more frequent.
* TODO Unused, but keeping for potential performance boost when local Bucket
* access de-serializes the entry (which could hurt perf.)
*
* @return the de-serialized value
*/
public final Object getDeserialized(boolean copyOnRead) {
if (isValueByteArray()) {
if (copyOnRead) {
// TODO move this code to CopyHelper.copy?
byte[] src = (byte[])this.rawValue;
byte[] dest = new byte[src.length];
System.arraycopy(this.rawValue, 0, dest, 0, dest.length);
return dest;
} else {
return this.rawValue;
}
} else if (this.rawValue instanceof CachedDeserializable) {
if (copyOnRead) {
return ((CachedDeserializable)this.rawValue).getDeserializedWritableCopy(null, null);
} else {
return ((CachedDeserializable)this.rawValue).getDeserializedForReading();
}
} else if (Token.isInvalid(this.rawValue)) {
return null;
} else {
if (copyOnRead) {
return CopyHelper.copy(this.rawValue);
} else {
return this.rawValue;
}
}
}
public final RawValue setFromCacheLoader() {
this.fromCacheLoader = true;
return this;
}
public final boolean isFromCacheLoader() {
return this.fromCacheLoader;
}
@Override
public String toString() {
return "RawValue(value=" + this.rawValue + ",isByteArray="
+ isValueByteArray() + ')';
}
}
/**
* Factory for {@link RawValue} instances. Overridden by GemFireXD to provide
* own RawValues that will also apply projection during serialization.
*
* @author swale
* @since 7.0
*/
public static class RawValueFactory {
private static final RawValueFactory _instance = new RawValueFactory();
protected RawValue newInstance(final Object rawVal) {
return new RawValue(rawVal);
}
public static RawValueFactory getFactory() {
return _instance;
}
}
private final int redundancy;
/** the partitioned region to which this bucket belongs */
protected final PartitionedRegion partitionedRegion;
private final Map pendingSecondaryExpires = new HashMap();
/* one map per bucket region */
private final HashMap allKeysMap =
new HashMap();
static final boolean FORCE_LOCAL_LISTENERS_INVOCATION =
Boolean.getBoolean("gemfire.BucketRegion.alwaysFireLocalListeners");
// gemfire.BucktRegion.alwaysFireLocalListeners=true
private volatile AtomicLong eventSeqNum = null;
public static final long INVALID_UUID = VMIdAdvisor.INVALID_ID;
public final ReentrantReadWriteLock columnBatchFlushLock =
new ReentrantReadWriteLock();
/**
* A read/write lock to prevent writing to the bucket when GII from this bucket is in progress
*/
private final ReentrantReadWriteWriteShareLock snapshotGIILock
= new ReentrantReadWriteWriteShareLock();
private final Object giiReadLockForSIOwner = new Object();
private final Object giiWriteLockForSIOwner = new Object();
private final boolean lockGIIForSnapshot =
Boolean.getBoolean("snappydata.snapshot.isolation.gii.lock");
public final AtomicLong getEventSeqNum() {
return eventSeqNum;
}
protected final AtomicReference hoplog = new AtomicReference();
public BucketRegion(String regionName, RegionAttributes attrs,
LocalRegion parentRegion, GemFireCacheImpl cache,
InternalRegionArguments internalRegionArgs) {
super(regionName, attrs, parentRegion, cache, internalRegionArgs);
if(PartitionedRegion.DISABLE_SECONDARY_BUCKET_ACK) {
Assert.assertTrue(attrs.getScope().isDistributedNoAck());
}
else {
Assert.assertTrue(attrs.getScope().isDistributedAck());
}
Assert.assertTrue(attrs.getDataPolicy().withReplication());
Assert.assertTrue( ! attrs.getEarlyAck());
Assert.assertTrue( ! attrs.getEnableGateway());
Assert.assertTrue(isUsedForPartitionedRegionBucket());
Assert.assertTrue( ! isUsedForPartitionedRegionAdmin());
Assert.assertTrue(internalRegionArgs.getBucketAdvisor() != null);
Assert.assertTrue(internalRegionArgs.getPartitionedRegion() != null);
this.redundancy = internalRegionArgs.getPartitionedRegionBucketRedundancy();
this.partitionedRegion = internalRegionArgs.getPartitionedRegion();
}
// Attempt to direct the GII process to the primary first
@Override
protected void initialize(InputStream snapshotInputStream,
InternalDistributedMember imageTarget,
InternalRegionArguments internalRegionArgs)
throws TimeoutException, IOException, ClassNotFoundException
{
// Set this region in the ProxyBucketRegion early so that profile exchange will
// perform the correct fillInProfile method
getBucketAdvisor().getProxyBucketRegion().setBucketRegion(this);
boolean success = false;
try {
if (this.partitionedRegion.isShadowPR()
&& this.partitionedRegion.getColocatedWith() != null) {
PartitionedRegion parentPR = ColocationHelper
.getLeaderRegion(this.partitionedRegion);
BucketRegion parentBucket = parentPR.getDataStore().getLocalBucketById(
getId());
// needs to be set only once.
if (parentBucket.eventSeqNum == null) {
parentBucket.eventSeqNum = new AtomicLong(getId());
}
}
if (this.partitionedRegion.getColocatedWith() == null) {
this.eventSeqNum = new AtomicLong(getId());
} else {
PartitionedRegion parentPR = ColocationHelper
.getLeaderRegion(this.partitionedRegion);
BucketRegion parentBucket = parentPR.getDataStore().getLocalBucketById(
getId());
if (parentBucket == null) {
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"The parentBucket of region"
+ this.partitionedRegion.getFullPath() + " bucketId "
+ getId() + " is NULL");
}
}
Assert.assertTrue(parentBucket != null);
this.eventSeqNum = parentBucket.eventSeqNum;
}
final InternalDistributedMember primaryHolder =
getBucketAdvisor().basicGetPrimaryMember();
if (primaryHolder != null && ! primaryHolder.equals(getMyId())) {
// Ignore the provided image target, use an existing primary (if any)
super.initialize(snapshotInputStream, primaryHolder, internalRegionArgs);
} else {
super.initialize(snapshotInputStream, imageTarget, internalRegionArgs);
}
success = true;
} finally {
if(!success) {
removeFromPeersAdvisors(false);
getBucketAdvisor().getProxyBucketRegion().clearBucketRegion(this);
}
}
}
@Override
public void initialized() {
//announce that the bucket is ready
//setHosting performs a profile exchange, so there
//is no need to call super.initialized() here.
}
@Override
protected DiskStoreImpl findDiskStore(RegionAttributes ra, InternalRegionArguments internalRegionArgs) {
return internalRegionArgs.getPartitionedRegion().getDiskStore();
}
@Override
public void createEventTracker() {
this.eventTracker = new EventTracker(this);
this.eventTracker.start();
}
@Override
protected CacheDistributionAdvisor createDistributionAdvisor(
InternalRegionArguments internalRegionArgs){
return internalRegionArgs.getBucketAdvisor();
}
public final BucketAdvisor getBucketAdvisor() {
return (BucketAdvisor)this.distAdvisor;
}
public final boolean isHosting() {
return getBucketAdvisor().isHosting();
}
/**
* Use it only when threads are doing incremental updates. If updates are random
* then this method may not be optimal.
*/
protected static boolean setIfGreater(AtomicLong l, long update) {
while (true) {
long cur = l.get();
if (update > cur) {
if (l.compareAndSet(cur, update))
return true;
} else
return false;
}
}
@Override
protected EventID distributeTombstoneGC(Set keysRemoved) {
EventID eventId = super.distributeTombstoneGC(keysRemoved);
if (keysRemoved != null && keysRemoved.size() > 0) {
// send the GC to members that don't have the bucket but have the PR so they
// can forward the event to clients
PRTombstoneMessage.send(this, keysRemoved, eventId);
}
return eventId;
}
@Override
protected void notifyClientsOfTombstoneGC(Map regionGCVersions, SetremovedKeys, EventID eventID, FilterInfo routing) {
if (CacheClientNotifier.getInstance() != null) {
// Only route the event to clients interested in the partitioned region.
// We do this by constructing a region-level event and then use it to
// have the filter profile ferret out all of the clients that have interest
// in this region
FilterProfile fp = getFilterProfile();
if (routing != null ||
(removedKeys != null && removedKeys.size() > 0 && fp != null)) { // fix for bug #46309 - don't send null/empty key set to clients
RegionEventImpl regionEvent = new RegionEventImpl(getPartitionedRegion(), Operation.REGION_DESTROY, null, true, getMyId());
FilterInfo clientRouting = routing;
if (clientRouting == null) {
clientRouting = fp.getLocalFilterRouting(regionEvent);
}
regionEvent.setLocalFilterInfo(clientRouting);
ClientUpdateMessage clientMessage = ClientTombstoneMessage.gc(getPartitionedRegion(), removedKeys,
eventID);
CacheClientNotifier.notifyClients(regionEvent, clientMessage);
}
}
}
/**
* Search the CM for keys. If found any, return the first found one
* Otherwise, save the keys into the CM, and return null
* The thread will acquire the lock before searching.
*
* @param keys
* @return first key found in CM
* null means not found
*/
private LockObject searchAndLock(Object keys[]) {
final LogWriterI18n logger = getCache().getLoggerI18n();
LockObject foundLock = null;
synchronized (allKeysMap) {
// check if there's any key in map
for (int i = 0; i < keys.length; i++) {
if ((foundLock = searchLock(keys[i], logger)) != null) {
break;
}
}
// save the keys when still locked
if (foundLock == null) {
for (int i = 0; i < keys.length; i++) {
addNewLock(keys[i], logger);
}
}
}
return foundLock;
}
private LockObject searchLock(final Object key, final LogWriterI18n logger) {
final LockObject foundLock;
if ((foundLock = allKeysMap.get(key)) != null) {
if (logger.finerEnabled()) {
logger.finer("LockKeys: found key: " + key + ":"
+ foundLock.lockedTimeStamp);
}
return foundLock;
}
return null;
}
private void addNewLock(final Object key, final LogWriterI18n logger) {
final LockObject lockValue = new LockObject(key,
logger.fineEnabled() ? System.currentTimeMillis() : 0);
allKeysMap.put(key, lockValue);
if (logger.finerEnabled()) {
logger.finer("LockKeys: add key: " + key + ":"
+ lockValue.lockedTimeStamp);
}
}
/**
* Search the CM for the key. If found, return the lock for the key.
* Otherwise, save the key into the CM, and return null
* The thread will acquire the lock before searching.
*
* @param key
*/
private LockObject searchAndLock(final Object key) {
final LogWriterI18n logger = getCache().getLoggerI18n();
LockObject foundLock = null;
synchronized (allKeysMap) {
// check if there's any key in map
if ((foundLock = searchLock(key, logger)) == null) {
// save the keys when still locked
addNewLock(key, logger);
}
}
return foundLock;
}
/**
* After processed the keys, this method will remove them from CM.
* And notifyAll for each key.
* The thread needs to acquire lock of CM first.
*
* @param keys
*/
public void removeAndNotifyKeys(Object keys[]) {
final LogWriterI18n logger = getCache().getLoggerI18n();
synchronized (allKeysMap) {
for (int i = 0; i < keys.length; i++) {
removeAndNotifyKeyNoLock(keys[i], logger);
} // for
}
}
private void removeAndNotifyKeyNoLock(final Object key,
final LogWriterI18n logger) {
{
{
LockObject lockValue = allKeysMap.remove(key);
if (lockValue != null) {
// let current thread become the monitor of the key object
synchronized (lockValue) {
lockValue.setRemoved();
if (logger.finerEnabled()) {
long waitTime = System.currentTimeMillis()
- lockValue.lockedTimeStamp;
logger.finer("LockKeys: remove key " + key
+ ", notifyAll for " + lockValue + ". It waited " + waitTime);
}
lockValue.notifyAll();
}
}
}
}
}
/**
* After processing the key, this method will remove it from CM.
* And notifyAll for each key.
* The thread needs to acquire lock of CM first.
*
* @param key
*/
public void removeAndNotifyKey(final Object key) {
final LogWriterI18n logger = getCache().getLoggerI18n();
synchronized (allKeysMap) {
removeAndNotifyKeyNoLock(key, logger);
}
}
/**
* Keep checking if CM has contained any key in keys. If yes, wait for notify,
* then retry again. This method will block current thread for long time.
* It only exits when current thread successfully save its keys into CM.
*
* @param keys
*/
public void waitUntilLocked(Object keys[]) {
final String title = "BucketRegion.waitUntilLocked:";
final LogWriterI18n logger = getCache().getLoggerI18n();
while (true) {
LockObject foundLock = searchAndLock(keys);
if (foundLock != null) {
waitForLock(foundLock, logger, title);
} else {
// now the keys have been locked by this thread
break;
} // to lock and process
} // while
}
private void waitForLock(final LockObject foundLock,
final LogWriterI18n logger, final String title) {
{
{
synchronized(foundLock) {
try {
while (!foundLock.isRemoved()) {
this.partitionedRegion.checkReadiness();
foundLock.wait(1000);
// primary could be changed by prRebalancing while waiting here
checkForPrimary();
}
}
catch (InterruptedException e) {
// TODO this isn't a localizable string and it's being logged at info level
logger.info(LocalizedStrings.DEBUG, title+" interrupted while waiting for "+foundLock+":"+e.getMessage());
}
if (logger.fineEnabled()) {
long waitTime = System.currentTimeMillis()-foundLock.lockedTimeStamp;
logger.fine(title+" waited " + waitTime + " ms to lock "+foundLock);
}
}
}
}
}
/**
* Keep checking if CM has contained any key in keys. If yes, wait for notify,
* then retry again. This method will block current thread for long time.
* It only exits when current thread successfully save its keys into CM.
*
* @param key
*/
public void waitUntilLocked(final Object key) {
final String title = "BucketRegion.waitUntilLocked:";
final LogWriterI18n logger = getCache().getLoggerI18n();
while (true) {
LockObject foundLock = searchAndLock(key);
if (foundLock != null) {
waitForLock(foundLock, logger, title);
} else {
// now the keys have been locked by this thread
break;
} // to lock and process
} // while
}
// this is in secondary bucket
@Override
boolean basicUpdate(final EntryEventImpl event,
final boolean ifNew,
final boolean ifOld,
final long lastModified,
final boolean overwriteDestroyed,
final boolean cacheWrite)
throws TimeoutException,
CacheWriterException {
// check validity of key against keyConstraint
if (this.keyConstraint != null) {
if (!this.keyConstraint.isInstance(event.getKey()))
throw new ClassCastException(LocalizedStrings.LocalRegion_KEY_0_DOES_NOT_SATISFY_KEYCONSTRAINT_1.
toLocalizedString(new Object[]{event.getKey().getClass().getName(), this.keyConstraint.getName()}));
}
validateValue(event.basicGetNewValue());
if (event.getTXState() != null && event.getTXState().isSnapshot()) {
return getSharedDataView().putEntry(event, ifNew, ifOld, null, false,
cacheWrite, lastModified, overwriteDestroyed);
} else {
return getDataView(event).putEntry(event, ifNew, ifOld, null, false,
cacheWrite, lastModified, overwriteDestroyed);
}
}
// Entry (Put/Create) rules
// If this is a primary for the bucket
// 1) apply op locally, aka update or create entry
// 2) distribute op to bucket secondaries and bridge servers with synchrony on local entry
// 3) cache listener with synchrony on entry
// Else not a primary
// 1) apply op locally
// 2) update local bs, gateway
@Override
protected
boolean virtualPut(EntryEventImpl event,
boolean ifNew,
boolean ifOld,
Object expectedOldValue,
boolean requireOldValue,
long lastModified,
boolean overwriteDestroyed)
throws TimeoutException,
CacheWriterException {
final boolean locked = beginLocalWrite(event);
boolean success = false;
try {
if (this.partitionedRegion.isLocalParallelWanEnabled()) {
handleWANEvent(event);
}
if (!hasSeenEvent(event)) {
forceSerialized(event);
RegionEntry oldEntry = this.entries
.basicPut(event, lastModified, ifNew, ifOld, expectedOldValue,
requireOldValue, overwriteDestroyed);
// if (DistributionManager.VERBOSE) {
// getCache().getLoggerI18n().info(LocalizedStrings.DEBUG,
// "BR.virtualPut: oldEntry returned = " + oldEntry + " so basic put returned: " + (oldEntry != null));
// }
success = true;
return oldEntry != null;
}
if (event.getDeltaBytes() != null && event.getRawNewValue() == null) {
// This means that this event has delta bytes but no full value.
// Request the full value of this event.
// The value in this vm may not be same as this event's value.
throw new InvalidDeltaException(
"Cache encountered replay of event containing delta bytes for key "
+ event.getKey());
}
// Forward the operation and event messages
// to members with bucket copies that may not have seen the event. Their
// EventTrackers will keep them from applying the event a second time if
// they've already seen it.
if (DistributionManager.VERBOSE) {
getCache().getLoggerI18n().info(LocalizedStrings.DEBUG,
"BR.virtualPut: this cache has already seen this event " + event);
}
distributeUpdateOperation(event, lastModified);
success = true;
return true;
} finally {
if (locked) {
endLocalWrite(event);
// create and insert column batch
TXStateInterface tx = event.getTXState(this);
if (success && checkForColumnBatchCreation(tx)) {
createAndInsertColumnBatch(tx, false);
}
if (success && partitionedRegion.isInternalColumnTable()) {
CallbackFactoryProvider.getStoreCallbacks()
.invokeColumnStorePutCallbacks(this, new EntryEventImpl[]{event});
}
}
}
}
public final boolean checkForColumnBatchCreation(TXStateInterface tx) {
final PartitionedRegion pr = getPartitionedRegion();
return pr.isRowBuffer()
&& (tx == null || !tx.getProxy().isColumnRolloverDisabled())
&& (getRegionSize() >= pr.getColumnMaxDeltaRows()
|| getTotalBytes() >= pr.getColumnBatchSize());
}
public final boolean createAndInsertColumnBatch(TXStateInterface tx,
boolean forceFlush) {
// do nothing if a flush is already in progress
if (this.columnBatchFlushLock.isWriteLocked()) {
return false;
}
final ReentrantReadWriteLock.WriteLock sync =
this.columnBatchFlushLock.writeLock();
sync.lock();
try {
return internalCreateAndInsertColumnBatch(tx, forceFlush);
} finally {
sync.unlock();
}
}
private boolean internalCreateAndInsertColumnBatch(TXStateInterface tx,
boolean forceFlush) {
// TODO: with forceFlush, ideally we should merge with an existing
// ColumnBatch if the current size to be flushed is small like < 1000
// (and split if total size has become too large)
boolean success = false;
boolean doFlush = false;
if (forceFlush) {
doFlush = getRegionSize() >= getPartitionedRegion()
.getColumnMinDeltaRows();
}
if (!doFlush) {
doFlush = checkForColumnBatchCreation(tx);
}
// we may have to use region.size so that no state
// has to be maintained
// one more check for size to make sure that concurrent call doesn't succeed.
// anyway batchUUID will be invalid in that case.
if (doFlush && getBucketAdvisor().isPrimary()) {
// need to flush the region
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine("createAndInsertColumnBatch: " +
"Creating the column batch for bucket " + this.getId());
}
final TXManagerImpl txManager = getCache().getCacheTransactionManager();
boolean txStarted = false;
if (tx == null && getCache().snapshotEnabled()) {
txManager.begin(IsolationLevel.SNAPSHOT, null);
txStarted = true;
}
try {
long batchId = partitionedRegion.newUUID(false);
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().info(LocalizedStrings.DEBUG, "createAndInsertCachedBatch: " +
"The snapshot after creating cached batch is " + getTXState().getLocalTXState().getCurrentSnapshot() +
" the current rvv is " + getVersionVector() + "batch id " + batchId);
}
//Check if shutdown hook is set
if (null != getCache().getRvvSnapshotTestHook()) {
getCache().notifyRvvTestHook();
getCache().waitOnRvvSnapshotTestHook();
}
Set keysToDestroy = createColumnBatchAndPutInColumnTable(batchId);
if (txManager.testRollBack) {
throw new RuntimeException("Test Dummy Exception");
}
destroyAllEntries(keysToDestroy, batchId);
//Check if shutdown hook is set
if (null != getCache().getRvvSnapshotTestHook()) {
getCache().notifyRvvTestHook();
getCache().waitOnRvvSnapshotTestHook();
}
success = true;
} finally {
if (txStarted) {
if (success) {
txManager.commit();
if (null != getCache().getRvvSnapshotTestHook()) {
getCache().notifyRvvTestHook();
}
} else {
txManager.rollback();
}
}
}
}
return success;
}
public static boolean isValidUUID(long uuid) {
return uuid != BucketRegion.INVALID_UUID;
}
private Set createColumnBatchAndPutInColumnTable(long key) {
StoreCallbacks callback = CallbackFactoryProvider.getStoreCallbacks();
return callback.createColumnBatch(this, key, this.getId());
}
// TODO: Suranjan Not optimized way to destroy all entries, as changes at level of RVV required.
// TODO: Suranjan Need to do something like putAll.
// TODO: Suranjan Effect of transaction?
// This destroy is under a lock which makes sure that there is no put into the region
// No need to take the lock on key
private void destroyAllEntries(Set keysToDestroy, long batchKey) {
for(Object key : keysToDestroy) {
if (getCache().getLoggerI18n().fineEnabled()) {
getCache()
.getLoggerI18n()
.fine("Destroying the entries after creating ColumnBatch " + key +
" batchid " + batchKey + " total size " + this.size() +
" keysToDestroy size " + keysToDestroy.size());
}
EntryEventImpl event = EntryEventImpl.create(
getPartitionedRegion(), Operation.DESTROY, null, null,
null, false, this.getMyId());
event.setKey(key);
event.setBucketId(this.getId());
TXStateInterface txState = event.getTXState(this);
if (txState != null) {
event.setRegion(this);
txState.destroyExistingEntry(event, true, null);
} else {
this.getPartitionedRegion().basicDestroy(event,true,null);
}
}
if (getCache().getLoggerI18n().fineEnabled()) {
getCache()
.getLoggerI18n()
.fine("Destroyed all for batchID " + batchKey + " total size " + this.size());
}
}
public void handleWANEvent(EntryEventImpl event) {
if (this.eventSeqNum == null) {
if (getCache().getLoggerI18n().fineEnabled()) {
getCache()
.getLoggerI18n()
.fine(
"The bucket corresponding to this user bucket is not created yet. This event will not go to remote wan site. Event: "
+ event);
}
}
if (!(this instanceof AbstractBucketRegionQueue)) {
if (getBucketAdvisor().isPrimary()) {
// see if it is a tx scenario
if(event.hasTX() && event.getTailKey() != -1) {
// DO Nothing
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On primary bucket " + getId() + " , tx phase1 has already set the seq number as "
+ event.getTailKey());
}
}
else {
event.setTailKey(this.eventSeqNum.addAndGet(this.partitionedRegion.getTotalNumberOfBuckets()));
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On primary bucket " + getId() + " , setting the seq number as "
+ this.eventSeqNum.get() + ". was it a tx operation? " + event.hasTX());
}
}
} else {
// Can there be a race here? Like one thread has done put in primary but
// its update comes later
// in that case its possible that a tail key is missed.
// we can handle that by only incrementing the tailKey and never
// setting it less than the current value.
setIfGreater(this.eventSeqNum, event.getTailKey());
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On secondary bucket " + getId() + " , setting the seq number as "
+ event.getTailKey() + ". was it a tx operation? " + event.hasTX());
}
}
}
}
public final long reserveWANSeqNumber(boolean checkWanPrimary) {
long reservedSeqNumber = -1;
final AtomicLong eventSeqNum = this.eventSeqNum;
if (eventSeqNum != null) {
final PartitionedRegion pr = this.partitionedRegion;
if (!checkWanPrimary
|| (pr.isLocalParallelWanEnabled() || pr.isHDFSRegion())
&& !(this instanceof AbstractBucketRegionQueue)) {
if (!checkWanPrimary || getBucketAdvisor().isPrimary()) {
reservedSeqNumber = eventSeqNum.addAndGet(pr
.getTotalNumberOfBuckets());
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On primary bucket " + getId()
+ " , setting the seq number as " + eventSeqNum.get());
}
}
else {
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On secondary bucket " + getId()
+ " , cannot reserve any seq number ");
}
}
}
}
else {
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"Cannot reserve as the bucket corresponding "
+ "to this user bucket is not created yet. ");
}
}
return reservedSeqNumber;
}
/**
* Fix for Bug#45917
* We are updating the seqNumber so that new seqNumbers are
* generated starting from the latest in the system.
* @param l
*/
public void updateEventSeqNum(long l) {
setIfGreater(this.eventSeqNum, l);
if (getCache().getLoggerI18n().fineEnabled()) {
getCache().getLoggerI18n().fine(
"WAN: On bucket " + getId() + " , setting the seq number as "
+ l + " , before GII");
}
}
protected void distributeUpdateOperation(EntryEventImpl event, long lastModified) {
if (!event.isOriginRemote()
&& !event.isNetSearch()
&& getBucketAdvisor().isPrimary()) {
if (event.getPutAllOperation() == null) {
new UpdateOperation(event, lastModified).distribute();
} else {
// consolidate the UpdateOperation for each entry into a PutAllMessage
// since we did not call basicPutPart3(), so we have to explicitly addEntry here
event.getPutAllOperation().addEntry(event, this.getId());
}
}
if (!event.getOperation().isPutAll()) { // putAll will invoke listeners later
event.invokeCallbacks(this, true, true);
}
}
/**
* distribute the operation in basicPutPart2 so the region entry lock is
* held
*/
@Override
protected long basicPutPart2(EntryEventImpl event, RegionEntry entry, boolean isInitialized,
long lastModified, boolean clearConflict) {
// Assumed this is called with entry synchrony
// Typically UpdateOperation is called with the
// timestamp returned from basicPutPart2, but as a bucket we want to do
// distribution *before* we do basicPutPart2.
final long modifiedTime = event.getEventTime(lastModified);
// Update the get stats if necessary.
if (this.partitionedRegion.getDataStore().hasClientInterest(event)) {
updateStatsForGet(entry, true);
}
if (!event.isOriginRemote()) {
if (event.getVersionTag() == null || event.getVersionTag().isGatewayTag()) {
LogWriterI18n log = getCache().getLoggerI18n();
boolean eventHasDelta = event.getDeltaBytes() != null;
VersionTag v = entry.generateVersionTag(null, false, eventHasDelta,
this, event);
if (v != null) {
if (log.fineEnabled()) { log.fine("generated version tag " + v + /*" for " + event.getKey() +*/ " in region " + this.getName()); }
}
}
// This code assumes it is safe ignore token mode (GII in progress)
// because it assumes when the origin of the event is local,
// the GII has completed and the region is initialized and open for local
// ops
long start = this.partitionedRegion.getPrStats().startSendReplication();
try {
if (event.getPutAllOperation() == null) {
Delta deltaNewValue = null;
if (event.isLoadedFromHDFS()) {
LogWriterI18n log = getCache().getLoggerI18n();
if (log.fineEnabled()) {
log.fine("Removing delta from the event for propagation of " +
"update operation as it is loaded from HDFS "
+ event);
}
// removing the delta so that old value gets replaced by new
// value on secondary for HDFS loaded event. As the row is not
// loaded on the secondary, delta can not be applied
deltaNewValue = event.getDeltaNewValue();
event.removeDelta();
}
UpdateOperation op = new UpdateOperation(event, modifiedTime);
op.distribute();
//set the delta back again
if (event.isLoadedFromHDFS() && (deltaNewValue != null)) {
event.setNewValue(deltaNewValue);
}
} else {
// consolidate the UpdateOperation for each entry into a PutAllMessage
// basicPutPart3 will call event.getPutAllOperation().addEntry(event);
}
} finally {
this.partitionedRegion.getPrStats().endSendReplication(start);
}
}
return super.basicPutPart2(event, entry, isInitialized, lastModified, clearConflict);
}
protected void notifyGatewayHubs(EnumListenerEvent operation,
EntryEventImpl event) {
if (shouldNotifyGatewayHub()) {
// We need to clone the event for PRs.
EntryEventImpl prEvent = createEventForPR(event);
try {
this.partitionedRegion.notifyGatewayHubs(operation, prEvent);
} finally {
prEvent.release();
}
} else {
// We don't need to clone the event for new Gateway Senders.
// Preserve the bucket reference for resetting it later.
LocalRegion bucketRegion = event.getRegion();
try {
event.setRegion(this.partitionedRegion);
this.partitionedRegion.notifyGatewayHubs(operation, event);
} finally {
// reset the event region back to bucket region.
// This should work as gateway queue create GatewaySenderEvent for queueing.
event.setRegion(bucketRegion);
}
}
}
public void checkForPrimary() {
final boolean isp = getBucketAdvisor().isPrimary();
if (! isp){
this.partitionedRegion.checkReadiness();
checkReadiness();
InternalDistributedMember primaryHolder = getBucketAdvisor().basicGetPrimaryMember();
throw new PrimaryBucketException("Bucket " + getName()
+ " is not primary. Current primary holder is "+primaryHolder);
}
}
/**
* Checks to make sure that this node is primary, and locks the bucket
* to make sure the bucket stays the primary bucket while the write
* is in progress. Any call to this method must be followed with a call
* to endLocalWrite().
* @param event
*/
private boolean beginLocalWrite(EntryEventImpl event) {
if (skipWriteLock(event)) {
return false;
}
if (cache.isCacheAtShutdownAll()) {
throw new CacheClosedException("Cache is shutting down");
}
final Object key = event.getKey();
waitUntilLocked(key); // it might wait for long time
boolean lockedForPrimary = false;
try {
doLockForPrimary(false, false);
return (lockedForPrimary = true);
} finally {
if (!lockedForPrimary) {
removeAndNotifyKey(key);
}
}
}
/**
* lock this bucket and, if present, its colocated "parent"
*
* @param tryLock whether to use tryLock (true) or a blocking lock (false)
* @param moveWriteLock whether to acquire write lock to block bucket move (true),
* or read lock (true); only bucket maintenance operations
* should acquire write lock
*
* @return true if locks were obtained and are still held
*/
public boolean doLockForPrimary(boolean tryLock, boolean moveWriteLock) {
boolean locked = moveWriteLock ? lockPrimaryStateWriteLock()
: lockPrimaryStateReadLock(tryLock);
if (!locked) {
return false;
}
boolean isPrimary = false;
try {
// Throw a PrimaryBucketException if this VM is assumed to be the
// primary but isn't, preventing update and distribution
checkForPrimary();
if (cache.isCacheAtShutdownAll()) {
throw new CacheClosedException("Cache is shutting down");
}
isPrimary = true;
} finally {
if (!isPrimary) {
if (moveWriteLock) doUnlockForPrimaryMove();
else doUnlockForPrimary();
}
}
return true;
}
private boolean lockPrimaryStateReadLock(boolean tryLock) {
Lock activeWriteLock = this.getBucketAdvisor().getActiveWriteLock();
Lock parentLock = this.getBucketAdvisor().getParentActiveWriteLock();
for (;;) {
boolean interrupted = Thread.interrupted();
try {
//Get the lock. If we have to wait here, it's because
//this VM is actively becoming "not primary". We don't want
//to throw an exception until this VM is actually no longer
//primary, so we wait here for not primary to complete. See bug #39963
if (parentLock != null) {
if (tryLock) {
boolean locked = parentLock.tryLock();
if (!locked) {
return false;
}
} else {
parentLock.lockInterruptibly();
}
if (tryLock) {
boolean locked = activeWriteLock.tryLock();
if (!locked) {
parentLock.unlock();
return false;
}
} else {
activeWriteLock.lockInterruptibly();
}
}
else {
if (tryLock) {
boolean locked = activeWriteLock.tryLock();
if (!locked) {
return false;
}
} else {
activeWriteLock.lockInterruptibly();
}
}
break; // success
} catch (InterruptedException e) {
interrupted = true;
cache.getCancelCriterion().checkCancelInProgress(null);
// don't throw InternalGemFireError to fix bug 40102
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
return true;
}
public void doUnlockForPrimary() {
Lock activeWriteLock = this.getBucketAdvisor().getActiveWriteLock();
activeWriteLock.unlock();
Lock parentLock = this.getBucketAdvisor().getParentActiveWriteLock();
if(parentLock!= null){
parentLock.unlock();
}
}
void doUnlockForPrimaryMove() {
getBucketAdvisor().getActivePrimaryMoveLock().unlock();
}
private boolean readLockEnabled() {
if (lockGIIForSnapshot) { // test hook
return true;
}
if ((this.getPartitionedRegion().isRowBuffer() ||
this.getPartitionedRegion().isInternalColumnTable()) &&
cache.snapshotEnabled()) {
return true;
} else {
return false;
}
}
private boolean writeLockEnabled() {
if (lockGIIForSnapshot) { // test hook
return true;
}
if ((isRowBuffer() || this.getPartitionedRegion().isInternalColumnTable()) &&
cache.snapshotEnabled()) {
return true;
} else {
return false;
}
}
private boolean lockPrimaryStateWriteLock() {
Lock activeMoveLock = this.getBucketAdvisor().getActivePrimaryMoveLock();
for (;;) {
boolean interrupted = Thread.interrupted();
try {
activeMoveLock.lockInterruptibly();
break; // success
} catch (InterruptedException e) {
interrupted = true;
cache.getCancelCriterion().checkCancelInProgress(null);
// don't throw InternalGemFireError to fix bug 40102
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
return true;
}
public void takeSnapshotGIIReadLock() {
if (readLockEnabled()) {
if (this.getPartitionedRegion().isInternalColumnTable()) {
BucketRegion bufferRegion = getBufferRegion();
bufferRegion.takeSnapshotGIIReadLock();
} else {
final LogWriterI18n logger = getCache().getLoggerI18n();
if (logger.fineEnabled()) {
logger.fine("Taking readonly snapshotGIILock on bucket " + this);
}
snapshotGIILock.attemptLock(LockMode.SH, -1, giiReadLockForSIOwner);
}
}
}
public void releaseSnapshotGIIReadLock() {
if (readLockEnabled()) {
if (this.getPartitionedRegion().isInternalColumnTable()) {
BucketRegion bufferRegion = getBufferRegion();
bufferRegion.releaseSnapshotGIIReadLock();
} else {
final LogWriterI18n logger = getCache().getLoggerI18n();
if (logger.fineEnabled()) {
logger.fine("Releasing readonly snapshotGIILock on bucket " + this.getName());
}
snapshotGIILock.releaseLock(LockMode.SH, false, giiReadLockForSIOwner);
}
}
}
private MembershipListener giiListener = null;
private volatile boolean snapshotGIILocked = false;
public boolean takeSnapshotGIIWriteLock(MembershipListener listener) {
if (writeLockEnabled()) {
if (this.getPartitionedRegion().isInternalColumnTable()) {
BucketRegion bufferRegion = getBufferRegion();
return bufferRegion.takeSnapshotGIIWriteLock(listener);
} else {
final LogWriterI18n logger = getCache().getLoggerI18n();
if (logger.fineEnabled()) {
logger.fine("Taking exclusive snapshotGIILock on bucket " + this.getName());
}
snapshotGIILock.attemptLock(LockMode.EX, -1, giiWriteLockForSIOwner);
getBucketAdvisor()
.addMembershipListenerAndAdviseGeneric(listener);
snapshotGIILocked = true;
this.giiListener = listener; // Set the listener only after taking the write lock.
if (logger.fineEnabled()) {
logger.fine("Succesfully took exclusive lock on bucket " + this.getName());
}
return true;
}
} else {
return false;
}
}
public void releaseSnapshotGIIWriteLock() {
if (writeLockEnabled()) {
if (this.getPartitionedRegion().isInternalColumnTable()) {
BucketRegion bufferRegion = getBufferRegion();
bufferRegion.releaseSnapshotGIIWriteLock();
} else {
final LogWriterI18n logger = getCache().getLoggerI18n();
if (logger.fineEnabled()) {
logger.fine("Releasing exclusive snapshotGIILock on bucket " + this.getName());
}
if (this.snapshotGIILock.hasExclusiveLock(giiWriteLockForSIOwner, null)) {
if (snapshotGIILocked) {
snapshotGIILock.releaseLock(LockMode.EX, false, giiWriteLockForSIOwner);
getBucketAdvisor().removeMembershipListener(giiListener);
this.giiListener = null;
snapshotGIILocked = false;
}
}
if (logger.fineEnabled()) {
logger.fine("Released exclusive snapshotGIILock on bucket " + this.getName());
}
}
}
}
private BucketRegion bufferRegion;
private final Object bufferRegionSync = new Object();
/**
* Corresponding bucket from row buffer if its a shadow table.
*
* @return
*/
public BucketRegion getBufferRegion() {
if (bufferRegion != null) {
return bufferRegion;
}
synchronized (bufferRegionSync) {
if (bufferRegion != null) {
return bufferRegion;
}
PartitionedRegion leaderReagion = ColocationHelper.getLeaderRegion(this.getPartitionedRegion());
this.bufferRegion = leaderReagion.getDataStore().getLocalBucketById(this.getId());
}
return bufferRegion;
}
/**
* Release the lock on the bucket that makes the bucket
* stay the primary during a write.
*/
private void endLocalWrite(EntryEventImpl event) {
doUnlockForPrimary();
removeAndNotifyKey(event.getKey());
}
protected boolean skipWriteLock(EntryEventImpl event) {
return event.isOriginRemote() || event.isNetSearch() || event.hasTX()
|| event.getOperation().isLocal() || event.getOperation().isPutAll()
|| (event.isExpiration() && (isEntryEvictDestroyEnabled() || event.isPendingSecondaryExpireDestroy()));
}
// this is stubbed out because distribution is done in basicPutPart2 while
// the region entry is still locked
@Override
protected void distributeUpdate(EntryEventImpl event, long lastModified) {
}
// Entry Invalidation rules
// If this is a primary for the bucket
// 1) apply op locally, aka update entry
// 2) distribute op to bucket secondaries and bridge servers with synchrony on local entry
// 3) cache listener with synchrony on entry
// 4) update local bs, gateway
// Else not a primary
// 1) apply op locally
// 2) update local bs, gateway
@Override
void basicInvalidate(EntryEventImpl event) throws EntryNotFoundException
{
basicInvalidate(event, isInitialized(), false);
}
@Override
void basicInvalidate(final EntryEventImpl event, boolean invokeCallbacks,
boolean forceNewEntry)
throws EntryNotFoundException {
// disallow local invalidation
Assert.assertTrue(! event.isLocalInvalid());
// avoid Assert that incurs expensive ThreadLocal lookup
assert !isTX(): "unexpected invocation with existing " + getTXState()
+ " for " + event;
Assert.assertTrue(event.getOperation().isDistributed());
final boolean locked = beginLocalWrite(event);
try {
// increment the tailKey so that invalidate operations are written to HDFS
if (this.partitionedRegion.hdfsStoreName != null) {
assert this.partitionedRegion.isLocalParallelWanEnabled();
handleWANEvent(event);
}
// which performs the local op.
// The ARM then calls basicInvalidatePart2 with the entry synchronized.
if ( !hasSeenEvent(event) ) {
if (event.getOperation().isExpiration()) { // bug 39905 - invoke listeners for expiration
DistributedSystem sys = cache.getDistributedSystem();
EventID newID = new EventID(sys);
event.setEventId(newID);
event.setInvokePRCallbacks(getBucketAdvisor().isPrimary());
}
boolean forceCallbacks = isEntryEvictDestroyEnabled();
boolean done = this.entries.invalidate(event, invokeCallbacks, forceNewEntry, forceCallbacks);
ExpirationAction expirationAction = getEntryExpirationAction();
if (done && !getBucketAdvisor().isPrimary() && expirationAction != null
&& expirationAction.isInvalidate()) {
synchronized(pendingSecondaryExpires) {
pendingSecondaryExpires.remove(event.getKey());
}
}
return;
}
else {
if (DistributionManager.VERBOSE) {
getCache().getLoggerI18n().info(
LocalizedStrings.DEBUG,
"LR.basicInvalidate: this cache has already seen this event " + event);
}
if (!event.isOriginRemote()
&& getBucketAdvisor().isPrimary()) {
// This cache has processed the event, forward operation
// and event messages to backup buckets
new InvalidateOperation(event).distribute();
}
event.invokeCallbacks(this,true, false);
return;
}
} finally {
if (locked) {
endLocalWrite(event);
}
}
}
@Override
void basicInvalidatePart2(final RegionEntry re, final EntryEventImpl event,
boolean clearConflict, boolean invokeCallbacks)
{
// Assumed this is called with the entry synchronized
if (!event.isOriginRemote()) {
if (event.getVersionTag() == null || event.getVersionTag().isGatewayTag()) {
LogWriterI18n log = getCache().getLoggerI18n();
VersionTag v = re.generateVersionTag(null, false, false, this, event);
if (log.fineEnabled() && v != null) { log.fine("generated version tag " + v + /*" for " + event.getKey() +*/ " in region " + this.getName()); }
event.setVersionTag(v);
}
// This code assumes it is safe ignore token mode (GII in progress)
// because it assumes when the origin of the event is local,
// the GII has completed and the region is initialized and open for local
// ops
// This code assumes that this bucket is primary
// distribute op to bucket secondaries and event to other listeners
InvalidateOperation op = new InvalidateOperation(event);
op.distribute();
}
super.basicInvalidatePart2(re, event, clearConflict /*Clear conflict occurred */, invokeCallbacks);
}
@Override
void distributeInvalidate(EntryEventImpl event) {
}
@Override
protected void distributeInvalidateRegion(RegionEventImpl event) {
// switch region in event so that we can have distributed region
// send InvalidateRegion message.
event.region = this;
super.distributeInvalidateRegion(event);
event.region = this.partitionedRegion;
}
@Override
protected boolean shouldDistributeInvalidateRegion(RegionEventImpl event) {
return getBucketAdvisor().isPrimary();
}
@Override
protected boolean shouldGenerateVersionTag(RegionEntry entry, EntryEventImpl event) {
if (event.getOperation().isLocal()) { // bug #45402 - localDestroy generated a version tag
return false;
}
return this.concurrencyChecksEnabled && ((event.getVersionTag() == null) || event.getVersionTag().isGatewayTag());
}
@Override
void expireDestroy(EntryEventImpl event, boolean cacheWrite) {
/* Early out before we throw a PrimaryBucketException because we're not primary */
if (!skipWriteLock(event) && !getBucketAdvisor().isPrimary()) {
return;
}
try {
super.expireDestroy(event, cacheWrite);
return;
} catch(PrimaryBucketException e) {
//must have concurrently removed the primary
return;
}
}
@Override
void expireInvalidate(EntryEventImpl event) {
if(!getBucketAdvisor().isPrimary()) {
return;
}
try {
super.expireInvalidate(event);
} catch (PrimaryBucketException e) {
//must have concurrently removed the primary
}
}
@Override
final void performExpiryTimeout(ExpiryTask p_task) throws CacheException
{
ExpiryTask task = p_task;
boolean isEvictDestroy = isEntryEvictDestroyEnabled();
//Fix for bug 43805 - get the primary lock before
//synchronizing on pendingSecondaryExpires, to match the lock
//ordering in other place (like acquiredPrimaryLock)
lockPrimaryStateReadLock(false);
try {
// Why do we care if evict destroy is configured?
// See bug 41096 for the answer.
if(!getBucketAdvisor().isPrimary() && !isEvictDestroy) {
synchronized (this.pendingSecondaryExpires) {
if (task.isPending()) {
Object key = task.getKey();
if (key != null) {
this.pendingSecondaryExpires.put(key, task);
}
}
}
} else {
super.performExpiryTimeout(task);
}
} finally {
doUnlockForPrimary();
}
}
protected boolean isEntryEvictDestroyEnabled() {
return getEvictionAttributes() != null && EvictionAction.LOCAL_DESTROY.equals(getEvictionAttributes().getAction());
}
protected final void processPendingSecondaryExpires()
{
ExpiryTask[] tasks;
while (true) {
// note we just keep looping until no more pendingExpires exist
synchronized (this.pendingSecondaryExpires) {
if (this.pendingSecondaryExpires.isEmpty()) {
return;
}
tasks = new ExpiryTask[this.pendingSecondaryExpires.size()];
tasks = this.pendingSecondaryExpires.values().toArray(tasks);
this.pendingSecondaryExpires.clear();
}
try {
if (isCacheClosing() || isClosed() || this.isDestroyed) {
return;
}
final LogWriterI18n logger = getCache().getLoggerI18n();
for (int i = 0; i < tasks.length; i++) {
try {
if (logger.fineEnabled()) {
logger.fine(tasks[i].toString() + " fired at "
+ System.currentTimeMillis());
}
tasks[i].basicPerformTimeout(true);
if (isCacheClosing() || isClosed() || isDestroyed()) {
return;
}
}
catch (EntryNotFoundException ignore) {
// ignore and try the next expiry task
}
}
}
catch (RegionDestroyedException re) {
// Ignore - our job is done
}
catch (CancelException ex) {
// ignore
}
catch (Throwable ex) {
Error err;
if (ex instanceof Error && SystemFailure.isJVMFailureError(
err = (Error)ex)) {
SystemFailure.initiateFailure(err);
// If this ever returns, rethrow the error. We're poisoned
// now, so don't let this thread continue.
throw err;
}
// 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();
getCache().getLoggerI18n().severe(LocalizedStrings.LocalRegion_EXCEPTION_IN_EXPIRATION_TASK, ex);
}
}
}
/**
* Creates an event for the EVICT_DESTROY operation so that events will fire
* for Partitioned Regions.
* @param key - the key that this event is related to
* @return an event for EVICT_DESTROY
*/
@Override
protected EntryEventImpl generateEvictDestroyEvent(Object key) {
EntryEventImpl event = super.generateEvictDestroyEvent(key);
event.setInvokePRCallbacks(true); //see bug 40797
return event;
}
// Entry Destruction rules
// If this is a primary for the bucket
// 1) apply op locally, aka destroy entry (REMOVED token)
// 2) distribute op to bucket secondaries and bridge servers with synchrony on local entry
// 3) cache listener with synchrony on local entry
// 4) update local bs, gateway
// Else not a primary
// 1) apply op locally
// 2) update local bs, gateway
@Override
protected
void basicDestroy(final EntryEventImpl event,
final boolean cacheWrite,
Object expectedOldValue)
throws EntryNotFoundException, CacheWriterException, TimeoutException {
// avoid Assert that incurs expensive ThreadLocal lookup
// assert (!(isTX() && !event.getOperation().isEviction())): "unexpected invocation with existing " + getTXState()
// + " for " + event;
Assert.assertTrue(event.getOperation().isDistributed());
final boolean locked = beginLocalWrite(event);
try {
// increment the tailKey for the destroy event
if (this.partitionedRegion.isLocalParallelWanEnabled()) {
handleWANEvent(event);
}
LogWriterI18n log = getCache().getLoggerI18n();
// In GemFire EVICT_DESTROY is not distributed, so in order to remove the entry
// from memory, allow the destroy to proceed. fixes #49784
if (GemFireCacheImpl.gfxdSystem() && event.isLoadedFromHDFS() && !getBucketAdvisor().isPrimary()) {
if (log.fineEnabled()) {
log.fine("Put the destory event in HDFS queue on secondary "
+ "and return as event is HDFS loaded " + event);
}
notifyGatewayHubs(EnumListenerEvent.AFTER_DESTROY, event);
return;
}else{
if (log.fineEnabled()) {
if (GemFireCacheImpl.gfxdSystem()) {
log.fine("Going ahead with the destroy as not hdfs loaded event " + event + " am i primary " + getBucketAdvisor().isPrimary());
} else {
log.fine("Going ahead with the destroy on GemFire system");
}
}
}
// This call should invoke AbstractRegionMap (aka ARM) destroy method
// which calls the CacheWriter, then performs the local op.
// The ARM then calls basicDestroyPart2 with the entry synchronized.
if ( !hasSeenEvent(event) ) {
if (event.getOperation().isExpiration()) { // bug 39905 - invoke listeners for expiration
DistributedSystem sys = cache.getDistributedSystem();
EventID newID = new EventID(sys);
event.setEventId(newID);
event.setInvokePRCallbacks(getBucketAdvisor().isPrimary());
}
boolean done = mapDestroy(event,
cacheWrite,
event.getOperation().isEviction(), // isEviction
expectedOldValue);
if(done && !getBucketAdvisor().isPrimary() && isEntryExpiryPossible()) {
synchronized(pendingSecondaryExpires) {
pendingSecondaryExpires.remove(event.getKey());
}
}
return;
}
else {
distributeDestroyOperation(event);
return;
}
} finally {
if (locked) {
endLocalWrite(event);
}
}
}
protected void distributeDestroyOperation (EntryEventImpl event) {
if (DistributionManager.VERBOSE) {
getCache().getLoggerI18n().info(
LocalizedStrings.DEBUG,
"BR.basicDestroy: this cache has already seen this event " + event);
}
if (!event.isOriginRemote()
&& getBucketAdvisor().isPrimary()) {
// This cache has processed the event, forward operation
// and event messages to backup buckets
event.setOldValueFromRegion();
new DestroyOperation(event).distribute();
}
event.invokeCallbacks(this,true, false);
}
@Override
protected void basicDestroyBeforeRemoval(RegionEntry entry, EntryEventImpl event) {
// Assumed this is called with entry synchrony
if (!event.isOriginRemote()
&& !event.getOperation().isLocal()
&& !Operation.EVICT_DESTROY.equals(event.getOperation())
&& !(event.isExpiration() && isEntryEvictDestroyEnabled())) {
if (event.getVersionTag() == null || event.getVersionTag().isGatewayTag()) {
LogWriterI18n log = getCache().getLoggerI18n();
VersionTag v = entry.generateVersionTag(null, false, false, this, event);
if (log.fineEnabled() && v != null) { log.fine("generated version tag " + v + /*" for " + event.getKey() +*/ " in region " + this.getName()); }
}
// This code assumes it is safe ignore token mode (GII in progress)
// because it assume when the origin of the event is local,
// then GII has completed (the region has been completely initialized)
// This code assumes that this bucket is primary
new DestroyOperation(event).distribute();
}
super.basicDestroyBeforeRemoval(entry, event);
}
@Override
void distributeDestroy(EntryEventImpl event, Object expectedOldValue) {
}
// impl removed - not needed for listener invocation alterations
// void basicDestroyPart2(RegionEntry re, EntryEventImpl event, boolean inTokenMode, boolean invokeCallbacks)
/*
@Override
protected void validateArguments(Object key, Object value,
Object aCallbackArgument) {
// LocalRegion#supportsTransaction already ensures that TX is always null
// for BucketRegions so no need for check below
assert !isTX(); // avoid Assert that incurs expensive ThreadLocal lookup
super.validateArguments(key, value, aCallbackArgument);
}
*/
public void forceSerialized(EntryEventImpl event) {
event.makeSerializedNewValue();
// Object obj = event.getRawNewValue();
// if (obj instanceof byte[]
// || obj == null
// || obj instanceof CachedDeserializable
// || obj == NotAvailable.NOT_AVAILABLE
// || Token.isInvalidOrRemoved(obj)) {
// // already serialized
// return;
// }
// throw new InternalGemFireError("event did not force serialized: " + event);
}
/**
* This method is called when a miss from a get ends up
* finding an object through a cache loader or from a server.
* In that case we want to make sure that we don't move
* this bucket while putting the value in the ache.
* @see LocalRegion#basicPutEntry(EntryEventImpl, long)
*/
@Override
protected RegionEntry basicPutEntry(final EntryEventImpl event,
final long lastModified) throws TimeoutException, CacheWriterException {
final boolean locked = beginLocalWrite(event);
try {
event.setInvokePRCallbacks(true);
forceSerialized(event);
return super.basicPutEntry(event, lastModified);
} finally {
if (locked) {
endLocalWrite(event);
}
}
}
@Override
void basicUpdateEntryVersion(EntryEventImpl event)
throws EntryNotFoundException {
Assert.assertTrue(!isTX());
Assert.assertTrue(event.getOperation().isDistributed());
beginLocalWrite(event);
try {
if (!hasSeenEvent(event)) {
this.entries.updateEntryVersion(event);
} else {
if (DistributionManager.VERBOSE) {
getCache().getLoggerI18n().info(
LocalizedStrings.DEBUG,
"BR.basicUpdateEntryVersion: this cache has already seen this event "
+ event);
}
}
if (!event.isOriginRemote() && getBucketAdvisor().isPrimary()) {
// This cache has processed the event, forward operation
// and event messages to backup buckets
new UpdateEntryVersionOperation(event).distribute();
}
return;
} finally {
endLocalWrite(event);
}
}
public int getRedundancyLevel()
{
return this.redundancy;
}
public boolean isPrimary() {
throw new UnsupportedOperationException(LocalizedStrings.BucketRegion_THIS_SHOULD_NEVER_BE_CALLED_ON_0.toLocalizedString(getClass()));
}
@Override
public final boolean isDestroyed() {
//TODO prpersist - Added this if null check for the partitioned region
// because we create the disk store for a bucket *before* in the constructor
// for local region, which is before this final field is assigned. This is why
// we shouldn't do some much work in the constructors! This is a temporary
// hack until I move must of the constructor code to region.initialize.
return isBucketDestroyed()
|| (this.partitionedRegion != null
&& this.partitionedRegion.isLocallyDestroyed && !isInDestroyingThread());
}
/**
* Return true if this bucket has been destroyed.
* Don't bother checking to see if the PR that owns this bucket was destroyed;
* that has already been checked.
* @since 6.0
*/
public final boolean isBucketDestroyed() {
return super.isDestroyed();
}
@Override
public String getColumnCompressionCodec() {
return this.partitionedRegion.getColumnCompressionCodec();
}
@Override
public boolean isHDFSRegion() {
return this.partitionedRegion.isHDFSRegion();
}
@Override
public boolean isHDFSReadWriteRegion() {
return this.partitionedRegion.isHDFSReadWriteRegion();
}
@Override
protected boolean isHDFSWriteOnly() {
return this.partitionedRegion.isHDFSWriteOnly();
}
@Override
public int sizeEstimate() {
if (isHDFSReadWriteRegion()) {
try {
checkForPrimary();
ConcurrentParallelGatewaySenderQueue q = getHDFSQueue();
if (q == null) return 0;
int hdfsBucketRegionSize = q.getBucketRegionQueue(
partitionedRegion, getId()).size();
int hoplogEstimate = (int) getHoplogOrganizer().sizeEstimate();
if (getLogWriterI18n().fineEnabled()) {
getLogWriterI18n().fine("for bucket " + getName() + " estimateSize returning "
+ (hdfsBucketRegionSize + hoplogEstimate));
}
return hdfsBucketRegionSize + hoplogEstimate;
} catch (ForceReattemptException e) {
throw new PrimaryBucketException(e.getLocalizedMessage(), e);
}
}
return size();
}
@Override
public void checkReadiness()
{
super.checkReadiness();
if (isDestroyed()) {
throw new RegionDestroyedException(toString(), getFullPath());
}
}
@Override
public final PartitionedRegion getPartitionedRegion(){
return this.partitionedRegion;
}
public BucketRegion getHostedBucketRegion() {
return this;
}
/**
* is the current thread involved in destroying the PR that
* owns this region?
*/
private final boolean isInDestroyingThread() {
return this.partitionedRegion.locallyDestroyingThread
== Thread.currentThread();
}
// public int getSerialNumber() {
// String s = "This should never be called on " + getClass();
// throw new UnsupportedOperationException(s);
// }
@Override
public void fillInProfile(Profile profile) {
super.fillInProfile(profile);
BucketProfile bp = (BucketProfile) profile;
bp.isInitializing = this.initializationLatchAfterGetInitialImage.getCount() > 0;
}
/** check to see if the partitioned region is locally destroyed or closed */
public boolean isPartitionedRegionOpen() {
return !this.partitionedRegion.isLocallyDestroyed &&
!this.partitionedRegion.isClosed && !this.partitionedRegion.isDestroyed();
}
/**
* Horribly plagiarized from the similar method in LocalRegion
*
* @param key
* @param updateStats
* @param clientEvent holder for client version tag
* @param returnTombstones whether Token.TOMBSTONE should be returned for destroyed entries
* @return serialized form if present, null if the entry is not in the cache,
* or INVALID or LOCAL_INVALID re is a miss (invalid)
* @throws IOException
* if there is a serialization problem
* @see LocalRegion#getDeserializedValue(RegionEntry, Object, Object, boolean,
* boolean, boolean, TXStateInterface, EntryEventImpl, boolean, boolean)
*/
private RawValue getSerialized(Object key, boolean updateStats,
boolean doNotLockEntry, final TXStateInterface lockState,
EntryEventImpl clientEvent, boolean returnTombstones, boolean allowReadFromHDFS)
throws EntryNotFoundException, IOException {
RegionEntry re = null;
if (allowReadFromHDFS) {
re = this.entries.getEntry(key);
} else {
re = this.entries.getOperationalEntryInVM(key);
}
if (re == null) {
return RawValue.NULLVALUE;
}
if (re.isTombstone() && !returnTombstones) {
return RawValue.NULLVALUE;
}
Object v = null;
try {
if (lockState != null) {
v = lockState.lockEntryForRead(re, key, this,
doNotLockEntry ? ReadEntryUnderLock.DO_NOT_LOCK_ENTRY : 0,
returnTombstones, READ_SER_VALUE);
}
else {
v = getSerialized(re, doNotLockEntry); // TODO OFFHEAP: todo v ends up in a RawValue. For now this can be a copy of the offheap onto the heap. But it might be easy to track lifetime of RawValue
}
if (v == RawValue.REQUIRES_ENTRY_LOCK) {
return RawValue.REQUIRES_ENTRY_LOCK;
}
if (clientEvent != null) {
VersionStamp stamp = re.getVersionStamp();
if (stamp != null) {
clientEvent.setVersionTag(stamp.asVersionTag());
}
}
} catch (DiskAccessException dae) {
this.handleDiskAccessException(dae, true /* stop bridge servers */);
throw dae;
}
if (v == null) {
return RawValue.NULLVALUE;
}
else {
if (updateStats) {
updateStatsForGet(re, true);
}
return RawValue.newInstance(v, this.cache);
}
}
private final Object getSerialized(final RegionEntry re,
final boolean doNotLockEntry) {
// TODO OFFHEAP: todo v ends up in a RawValue. For now this can be a copy of
// the offheap onto the heap. But it might be easy to track lifetime of
// RawValue
Object v = re.getValue(this);
if (doNotLockEntry) {
if (v == Token.NOT_AVAILABLE || v == null) {
return RawValue.REQUIRES_ENTRY_LOCK;
}
}
return v;
}
/**
* Return serialized form of an entry
*
* Horribly plagiarized from the similar method in LocalRegion
*
* @param keyInfo
* @param generateCallbacks
* @param clientEvent holder for the entry's version information
* @param returnTombstones TODO
* @return serialized (byte) form
* @throws IOException if the result is not serializable
* @see LocalRegion#get(Object, Object, boolean, EntryEventImpl)
*/
public RawValue getSerialized(final Object key, final Object callbackArg,
KeyInfo keyInfo, boolean generateCallbacks, boolean doNotLockEntry,
final TXStateInterface lockState, EntryEventImpl clientEvent,
boolean returnTombstones, boolean allowReadFromHDFS) throws IOException {
checkReadiness();
checkForNoAccess();
CachePerfStats stats = getCachePerfStats();
long start = stats.startGet();
boolean miss = true;
try {
boolean isCreate = false;
RawValue result = getSerialized(key, true, doNotLockEntry, lockState,
clientEvent, returnTombstones, allowReadFromHDFS);
isCreate = result == RawValue.NULLVALUE
|| (result.getRawValue() == Token.TOMBSTONE && !returnTombstones);
miss = (result == RawValue.NULLVALUE || Token.isInvalid(result
.getRawValue()));
if (miss) {
// if scope is local and there is no loader, then
// don't go further to try and get value
if (hasServerProxy() ||
basicGetLoader() != null) {
if(doNotLockEntry) {
return RawValue.REQUIRES_ENTRY_LOCK;
}
if (keyInfo == null) {
keyInfo = new KeyInfo(key, callbackArg, getId());
}
// TODO OFFHEAP: optimze
Object value = nonTxnFindObject(keyInfo, isCreate,
generateCallbacks, result.getRawValue(), true, true, clientEvent, false, allowReadFromHDFS);
if (value != null) {
result = RawValue.newInstance(value, this.cache)
.setFromCacheLoader();
}
}
else { // local scope with no loader, still might need to update stats
if (isCreate) {
recordMiss(getTXState(), null, key);
}
}
}
// changed in 7.0 to return RawValue(Token.INVALID) if the entry is invalid
return result;
} finally {
stats.endGet(start, miss);
}
} // getSerialized
@Override
public final int hashCode() {
return RegionInfoShip
.getHashCode(this.partitionedRegion.getPRId(), getId());
}
@Override
protected StringBuilder getStringBuilder() {
return super.getStringBuilder().append("; serial=")
.append(getSerialNumber()).append("; primary=").append(
getBucketAdvisor().getProxyBucketRegion().isPrimary());
}
@Override
protected void distributedRegionCleanup(RegionEventImpl event)
{
// No need to close advisor, assume its already closed
// However we need to remove our listener from the advisor (see bug 43950).
this.distAdvisor.removeMembershipListener(this.advisorListener);
}
/**
* Tell the peers that this VM has destroyed the region.
*
* Also marks the local disk files as to be deleted before
* sending the message to peers.
*
*
* @param rebalance true if this is due to a rebalance removing the bucket
*/
public void removeFromPeersAdvisors(boolean rebalance) {
if(getPersistenceAdvisor() != null) {
getPersistenceAdvisor().releaseTieLock();
}
DiskRegion diskRegion = getDiskRegion();
//Tell our peers whether we are destroying this region
//or just closing it.
boolean shouldDestroy = rebalance || diskRegion == null
|| !diskRegion.isRecreated();
Operation op = shouldDestroy ? Operation.REGION_LOCAL_DESTROY
: Operation.REGION_CLOSE;
RegionEventImpl event = new RegionEventImpl(this, op, null, false,
getMyId(), generateEventID()/* generate EventID */);
// When destroying the whole partitioned region, there's no need to
// distribute the region closure/destruction, the PR RegionAdvisor.close()
// has taken care of it
if (isPartitionedRegionOpen()) {
//Only delete the files on the local disk if
//this is a rebalance, or we are creating the bucket
//for the first time
if (diskRegion != null && shouldDestroy) {
diskRegion.beginDestroyDataStorage();
}
//Send out the destroy op to peers
new DestroyRegionOperation(event, true).distribute();
}
}
@Override
protected void distributeDestroyRegion(RegionEventImpl event,
boolean notifyOfRegionDeparture) {
//No need to do this when we actually destroy the region,
//we already distributed this info.
}
EntryEventImpl createEventForPR(EntryEventImpl sourceEvent) {
EntryEventImpl e2 = new EntryEventImpl(sourceEvent);
boolean returned = false;
try {
e2.setRegion(this.partitionedRegion);
if (FORCE_LOCAL_LISTENERS_INVOCATION) {
e2.setInvokePRCallbacks(true);
}
else {
e2.setInvokePRCallbacks(sourceEvent.getInvokePRCallbacks());
}
DistributedMember dm = this.getDistributionManager().getDistributionManagerId();
e2.setOriginRemote(!e2.getDistributedMember().equals(dm));
returned = true;
return e2;
} finally {
if (!returned) {
e2.release();
}
}
}
@Override
public void invokeTXCallbacks(
final EnumListenerEvent eventType, final EntryEventImpl event,
final boolean callDispatchListenerEvent, final boolean notifyGateway)
{
if (getCache().getLogger().fineEnabled()) {
getCache().getLogger().fine("BR.invokeTXCallbacks for event " + event);
}
// bucket events may make it to this point even though the bucket is still
// initializing. We can't block while initializing or a GII state flush
// may hang, so we avoid notifying the bucket
if (this.isInitialized()) {
boolean callThem = callDispatchListenerEvent;
if (event.isPossibleDuplicate()
&& this.eventTracker.isInitialImageProvider(event.getDistributedMember())) {
callThem = false;
}
super.invokeTXCallbacks(eventType, event, callThem, notifyGateway);
}
final EntryEventImpl prevent = createEventForPR(event);
try {
this.partitionedRegion.invokeTXCallbacks(eventType, prevent, this.partitionedRegion.isInitialized() ? callDispatchListenerEvent : false, false);
} finally {
prevent.release();
}
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.internal.cache.LocalRegion#invokeDestroyCallbacks(com.gemstone.gemfire.internal.cache.EnumListenerEvent, com.gemstone.gemfire.internal.cache.EntryEventImpl, boolean)
*/
@Override
public void invokeDestroyCallbacks(
final EnumListenerEvent eventType, final EntryEventImpl event,
final boolean callDispatchListenerEvent, boolean notifyGateways)
{
// bucket events may make it to this point even though the bucket is still
// initializing. We can't block while initializing or a GII state flush
// may hang, so we avoid notifying the bucket
if (this.isInitialized()) {
boolean callThem = callDispatchListenerEvent;
if (event.isPossibleDuplicate()
&& this.eventTracker.isInitialImageProvider(event.getDistributedMember())) {
callThem = false;
}
super.invokeDestroyCallbacks(eventType, event, callThem, notifyGateways);
}
final EntryEventImpl prevent = createEventForPR(event);
try {
this.partitionedRegion.invokeDestroyCallbacks(eventType, prevent, this.partitionedRegion.isInitialized() ? callDispatchListenerEvent : false, false);
} finally {
prevent.release();
}
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.internal.cache.LocalRegion#invokeInvalidateCallbacks(com.gemstone.gemfire.internal.cache.EnumListenerEvent, com.gemstone.gemfire.internal.cache.EntryEventImpl, boolean)
*/
@Override
public void invokeInvalidateCallbacks(
final EnumListenerEvent eventType, final EntryEventImpl event,
final boolean callDispatchListenerEvent)
{
// bucket events may make it to this point even though the bucket is still
// initializing. We can't block while initializing or a GII state flush
// may hang, so we avoid notifying the bucket
if (this.isInitialized()) {
boolean callThem = callDispatchListenerEvent;
if (event.isPossibleDuplicate()
&& this.eventTracker.isInitialImageProvider(event.getDistributedMember())) {
callThem = false;
}
super.invokeInvalidateCallbacks(eventType, event, callThem);
}
final EntryEventImpl prevent = createEventForPR(event);
try {
this.partitionedRegion.invokeInvalidateCallbacks(eventType, prevent, this.partitionedRegion.isInitialized() ? callDispatchListenerEvent : false);
} finally {
prevent.release();
}
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.internal.cache.LocalRegion#invokePutCallbacks(com.gemstone.gemfire.internal.cache.EnumListenerEvent, com.gemstone.gemfire.internal.cache.EntryEventImpl, boolean)
*/
@Override
public void invokePutCallbacks(
final EnumListenerEvent eventType, final EntryEventImpl event,
final boolean callDispatchListenerEvent, boolean notifyGateways)
{
if (getCache().getLogger().finerEnabled()) {
getCache().getLogger().finer("invoking put callbacks on bucket for event " + event);
}
// bucket events may make it to this point even though the bucket is still
// initializing. We can't block while initializing or a GII state flush
// may hang, so we avoid notifying the bucket
if (this.isInitialized()) {
boolean callThem = callDispatchListenerEvent;
if (callThem && event.isPossibleDuplicate()
&& this.eventTracker.isInitialImageProvider(event.getDistributedMember())) {
callThem = false;
}
super.invokePutCallbacks(eventType, event, callThem, notifyGateways);
}
final EntryEventImpl prevent = createEventForPR(event);
try {
this.partitionedRegion.invokePutCallbacks(eventType, prevent,
this.partitionedRegion.isInitialized() ? callDispatchListenerEvent : false, false);
} finally {
prevent.release();
}
}
/**
* perform adjunct messaging for the given operation and return a set of
* members that should be attached to the operation's reply processor (if any)
* @param event the event causing this messaging
* @param cacheOpRecipients set of receiver which got cacheUpdateOperation.
* @param adjunctRecipients recipients that must unconditionally get the event
* @param filterRoutingInfo routing information for all members having the region
* @param processor the reply processor, or null if there isn't one
* @return the set of failed recipients
*/
protected Set performAdjunctMessaging(EntryEventImpl event,
Set cacheOpRecipients, Set adjunctRecipients,
FilterRoutingInfo filterRoutingInfo,
DirectReplyProcessor processor,
boolean calculateDelta,
boolean sendDeltaWithFullValue) {
Set failures = Collections.EMPTY_SET;
PartitionMessage msg = event.getPartitionMessage();
if (calculateDelta) {
setDeltaIfNeeded(event);
}
if (msg != null) {
// The primary bucket member which is being modified remotely by a GemFire
// thread via a received PartitionedMessage
//Asif: Some of the adjunct recepients include those members which
// are GemFireXDHub & would need old value along with news
msg = msg.getMessageForRelayToListeners(event, adjunctRecipients);
msg.setSender(this.partitionedRegion.getDistributionManager()
.getDistributionManagerId());
msg.setSendDeltaWithFullValue(sendDeltaWithFullValue);
failures = msg.relayToListeners(cacheOpRecipients, adjunctRecipients,
filterRoutingInfo, event, this.partitionedRegion, processor);
}
else {
// The primary bucket is being modified locally by an application thread locally
Operation op = event.getOperation();
if (op.isCreate() || op.isUpdate()) {
// note that at this point ifNew/ifOld have been used to update the
// local store, and the event operation should be correct
failures = PutMessage.notifyListeners(cacheOpRecipients,
adjunctRecipients, filterRoutingInfo, this.partitionedRegion,
event, op.isCreate(), !op.isCreate(), processor,
sendDeltaWithFullValue);
}
else if (op.isDestroy()) {
failures = DestroyMessage.notifyListeners(cacheOpRecipients,
adjunctRecipients, filterRoutingInfo,
this.partitionedRegion, event, processor);
}
else if (op.isInvalidate()) {
failures = InvalidateMessage.notifyListeners(cacheOpRecipients,
adjunctRecipients, filterRoutingInfo,
this.partitionedRegion, event, processor);
}
else {
failures = adjunctRecipients;
}
}
return failures;
}
private void setDeltaIfNeeded(EntryEventImpl event) {
Object instance;
if (this.partitionedRegion.getSystem().getConfig().getDeltaPropagation()
&& event.getOperation().isUpdate() && event.getDeltaBytes() == null) {
@Unretained Object rawNewValue = event.getRawNewValue();
if (!(rawNewValue instanceof CachedDeserializable)) {
return;
}
if (rawNewValue instanceof StoredObject && !((StoredObject) rawNewValue).isSerialized()) {
// it is a byte[]; not a Delta
return;
}
instance = ((CachedDeserializable)rawNewValue).getValue();
final com.gemstone.gemfire.Delta delta;
if (instance instanceof com.gemstone.gemfire.Delta
&& (delta = (com.gemstone.gemfire.Delta)instance).hasDelta()) {
try {
HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT);
long start = DistributionStats.getStatTime();
delta.toDelta(hdos);
event.setDeltaBytes(hdos.toByteArray());
this.partitionedRegion.getCachePerfStats().endDeltaPrepared(start);
}
catch (RuntimeException re) {
throw re;
}
catch (Exception e) {
throw new DeltaSerializationException(
LocalizedStrings.DistributionManager_CAUGHT_EXCEPTION_WHILE_SENDING_DELTA
.toLocalizedString(), e);
}
}
}
}
/**
* create a PutAllPRMessage for notify-only and send it to all adjunct nodes.
* return a set of members that should be attached to the operation's reply processor (if any)
* @param dpao DistributedPutAllOperation object for PutAllMessage
* @param cacheOpRecipients set of receiver which got cacheUpdateOperation.
* @param adjunctRecipients recipients that must unconditionally get the event
* @param filterRoutingInfo routing information for all members having the region
* @param processor the reply processor, or null if there isn't one
* @return the set of failed recipients
*/
public Set performPutAllAdjunctMessaging(DistributedPutAllOperation dpao,
Set cacheOpRecipients, Set adjunctRecipients, FilterRoutingInfo filterRoutingInfo,
DirectReplyProcessor processor) {
// create a PutAllPRMessage out of PutAllMessage to send to adjunct nodes
PutAllPRMessage prMsg = dpao.createPRMessagesNotifyOnly(getId());
prMsg.initMessage(this.partitionedRegion, adjunctRecipients, true, processor);
prMsg.setSender(this.partitionedRegion.getDistributionManager()
.getDistributionManagerId());
// find members who have clients subscribed to this event and add them
// to the recipients list. Also determine if there are any FilterInfo
// routing tables for any of the receivers
// boolean anyWithRouting = false;
Set recipients = null;
Set membersWithRouting = filterRoutingInfo.getMembers();
for (Iterator it=membersWithRouting.iterator(); it.hasNext(); ) {
Object mbr = it.next();
if (!cacheOpRecipients.contains(mbr)) {
// anyWithRouting = true;
if (!adjunctRecipients.contains(mbr)) {
if (recipients == null) {
recipients = new HashSet();
recipients.add(mbr);
}
}
}
}
if (recipients == null) {
recipients = adjunctRecipients;
} else {
recipients.addAll(adjunctRecipients);
}
// Set failures = Collections.EMPTY_SET;
// if (!anyWithRouting) {
Set failures = this.partitionedRegion.getDistributionManager().putOutgoing(prMsg);
// } else {
// // Send message to each member. We set a FilterRoutingInfo serialization
// // target so that serialization of the PutAllData objects held in the
// // message will only serialize the routing entry for the message recipient
// Iterator rIter = recipients.iterator();
// failures = new HashSet();
// while (rIter.hasNext()){
// InternalDistributedMember member = (InternalDistributedMember)rIter.next();
// FilterRoutingInfo.setSerializationTarget(member);
// try {
// prMsg.resetRecipients();
// prMsg.setRecipient(member);
// Set fs = this.partitionedRegion.getDistributionManager().putOutgoing(prMsg);
// if (fs != null && !fs.isEmpty()) {
// failures.addAll(fs);
// }
// } finally {
// FilterRoutingInfo.clearSerializationTarget();
// }
// }
// }
return failures;
}
/**
* return the set of recipients for adjunct operations
*/
protected Set getAdjunctReceivers(EntryEventImpl event, Set cacheOpReceivers,
Set twoMessages, FilterRoutingInfo routing) {
Operation op = event.getOperation();
if (op.isUpdate() || op.isCreate() || (op.isDestroy() && !op.isExpiration()) || op.isInvalidate()) {
// this method can safely assume that the operation is being distributed from
// the primary bucket holder to other nodes
Set r = this.partitionedRegion.getRegionAdvisor()
.adviseRequiresNotification(event);
if (r.size() > 0) {
r.removeAll(cacheOpReceivers);
}
// buckets that are initializing may transition out of token mode during
// message transmission and need both cache-op and adjunct messages to
// ensure that listeners are invoked
if (twoMessages.size() > 0) {
if (r.size() == 0) { // can't add to Collections.EMPTY_SET
r = twoMessages;
}
else {
r.addAll(twoMessages);
}
}
if (routing != null) {
// add adjunct messages to members with client routings
for (InternalDistributedMember id: routing.getMembers()) {
if (!cacheOpReceivers.contains(id)) {
if (r.isEmpty()) {
r = new HashSet();
}
r.add(id);
}
}
}
return r;
}
else {
return Collections.EMPTY_SET;
}
}
@Override
public final int getId() {
return getBucketAdvisor().getProxyBucketRegion().getId();
}
@Override
protected void cacheWriteBeforePut(EntryEventImpl event, Set netWriteRecipients,
CacheWriter localWriter,
boolean requireOldValue, Object expectedOldValue)
throws CacheWriterException, TimeoutException {
boolean origRemoteState = false;
try {
if (event.getPartitionMessage() != null || event.hasClientOrigin()) {
origRemoteState=event.isOriginRemote();
event.setOriginRemote(true);
}
event.setRegion(this.partitionedRegion);
this.partitionedRegion.cacheWriteBeforePut(event, netWriteRecipients,
localWriter, requireOldValue, expectedOldValue);
} finally {
if (event.getPartitionMessage() != null || event.hasClientOrigin()) {
event.setOriginRemote(origRemoteState);
}
event.setRegion(this);
}
}
@Override
boolean cacheWriteBeforeDestroy(EntryEventImpl event, Object expectedOldValue)
throws CacheWriterException, EntryNotFoundException, TimeoutException {
boolean origRemoteState = false;
boolean ret = false;
try {
if (event.getPartitionMessage() != null || event.hasClientOrigin()) {
origRemoteState=event.isOriginRemote();
event.setOriginRemote(true);
}
event.setRegion(this.partitionedRegion);
ret = this.partitionedRegion.cacheWriteBeforeDestroy(event, expectedOldValue);
} finally {
if (event.getPartitionMessage() != null || event.hasClientOrigin()) {
event.setOriginRemote(origRemoteState);
}
event.setRegion(this);
}
return ret;
// return super.cacheWriteBeforeDestroy(event);
}
@Override
public CacheWriter basicGetWriter() {
return this.partitionedRegion.basicGetWriter();
}
@Override
void cleanUpOnIncompleteOp(EntryEventImpl event, RegionEntry re,
boolean eventRecorded, boolean updateStats, boolean isReplace) {
if(!eventRecorded || isReplace) {
//No indexes updated so safe to remove.
this.entries.removeEntry(event.getKey(), re, updateStats) ;
}/*else {
//if event recorded is true, that means as per event tracker entry is in
//system. As per gemfirexd, indexes have been updated. What is not done
// is basicPutPart2( distribution etc). So we do nothing as PR's re-attempt
// will do the required basicPutPart2. If we remove the entry here, than
//event tracker will not allow re insertion. So either we do nothing or
//if we remove ,than we have to update gfxdindexes as well as undo recording
// of event.
//TODO:OQL indexes? : Hope they get updated during retry. The issue is that oql indexes
// get updated after distribute , so it is entirely possible that oql index are
// not updated. what if retry fails?
}*/
}
/* (non-Javadoc)
* @see com.gemstone.gemfire.internal.cache.partitioned.Bucket#getBucketOwners()
* @since gemfire59poc
*/
public Set getBucketOwners() {
return getBucketAdvisor().getProxyBucketRegion().getBucketOwners();
}
public long getCounter() {
return counter.get();
}
public void updateCounter(long delta) {
if (delta != 0) {
this.counter.getAndAdd(delta);
}
}
public void resetCounter() {
if (this.counter.get() != 0) {
this.counter.set(0);
}
}
public long getLimit() {
if (this.limit == null) {
return 0;
}
return limit.get();
}
public void setLimit(long limit) {
// This method can be called before object of this class is created
if (this.limit == null) {
this.limit = new AtomicLong();
}
this.limit.set(limit);
}
static int calcMemSize(Object value) {
if (value == null || value instanceof Token) {
return 0;
}
if (value instanceof GatewaySenderEventImpl) {
return ((GatewaySenderEventImpl)value).getSerializedValueSize();
}
if (!(value instanceof byte[])
&& !CachedDeserializableFactory.preferObject()
&& !(value instanceof CachedDeserializable)
&& !(value instanceof com.gemstone.gemfire.Delta)
&& !(value instanceof Delta)) {
// ezoerner:20090401 it's possible this value is a Delta
throw new InternalGemFireError("DEBUG: calcMemSize: weird value (class "
+ value.getClass() + "): " + value);
}
try {
return CachedDeserializableFactory.calcMemSize(value);
} catch (IllegalArgumentException e) {
return 0;
}
}
boolean isDestroyingDiskRegion;
@Override
protected void updateSizeOnClearRegion(int sizeBeforeClear) {
// This method is only called when the bucket is destroyed. If we
// start supporting clear of partitioned regions, this logic needs to change
// we can't just set these counters to zero, because there could be
// concurrent operations that are also updating these stats. For example,
//a destroy could have already been applied to the map, and then updates
//the stat after we reset it, making the state negative.
final PartitionedRegionDataStore prDs = this.partitionedRegion.getDataStore();
// this.debugMap.clear();
// this.createCount.set(0);
// this.putCount.set(0);
// this.invalidateCount.set(0);
// this.evictCount.set(0);
// this.faultInCount.set(0);
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnClearRegion (" + this + ")");
// }
long oldMemValue;
if (!this.reservedTable() && needAccounting()) {
long ignoreBytes = (this.isDestroyed || this.isDestroyingDiskRegion) ? getIgnoreBytes() :
getIgnoreBytes() + regionOverHead;
callback.dropStorageMemory(getFullPath(), ignoreBytes);
}
if(this.isDestroyed || this.isDestroyingDiskRegion) {
//If this region is destroyed, mark the stat as destroyed.
oldMemValue = this.bytesInMemory.getAndSet(BUCKET_DESTROYED);
} else if(!this.isInitialized()) {
//This case is rather special. We clear the region if the GII failed.
//In the case of bucket regions, we know that there will be no concurrent operations
//if GII has failed, because there is not primary. So it's safe to set these
//counters to 0.
oldMemValue = this.bytesInMemory.getAndSet(0);
}
// GemFireXD has table/DD locks, so clearing the flags is fine
else if (GemFireCacheImpl.gfxdSystem()) {
oldMemValue = this.bytesInMemory.getAndSet(0);
}
else {
throw new InternalGemFireError("Trying to clear a bucket region that was not destroyed or in initialization.");
}
if(oldMemValue != BUCKET_DESTROYED) {
this.partitionedRegion.getPrStats().incDataStoreEntryCount(-sizeBeforeClear);
prDs.updateMemoryStats(-oldMemValue);
}
// explicitly clear overflow counters if no diskRegion is present
// (for latter the counters are cleared by DiskRegion.statsClear)
if (getDiskRegion() == null) {
this.numOverflowOnDisk.set(0);
this.numOverflowBytesOnDisk.set(0);
}
}
@Override
public int calculateValueSize(Object val) {
// Only needed by BucketRegion
return calcMemSize(val);
}
@Override
public int calculateRegionEntryValueSize(RegionEntry re) {
return calcMemSize(re._getValue()); // OFFHEAP _getValue ok
}
@Override
void updateSizeOnPut(Object key, int oldSize, int newSize) {
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnPut (" + this
// + " key=" + key
// + ") oldSize = " + oldSize + " newSize = " + newSize
// + " cCount=" + this.createCount.get()
// + " rCount=" + this.removeCount.get()
// + " pCount=" + this.putCount.get()
// + " iCount=" + this.invalidateCount.get()
// + " eCount=" + this.evictCount.get()
// + " fCount=" + this.faultInCount.get()
// + " liveCount=" + getLiveCount()
// + " bSize=" + this.bytesInMemory.get());
// }
// if (newSize == 0) {
// if (oldSize == 0 && oldVal != NotAvailable.NOT_AVAILABLE
// && oldVal != Token.REMOVED_PHASE1) {
// cache.getLogger().info("called updateSizeOnPut with oldVal=" + oldVal + " newVal=" + newVal,
// new RuntimeException("STACK"));
// } else {
// if (oldSize != 0) {
// this.invalidateCount.incrementAndGet();
// }
// if (oldVal != Token.REMOVED_PHASE1) {
// Assert.assertTrue(this.debugMap.containsKey(key));
// } else {
// // create invalid. No need to change stats
// Assert.assertTrue(!this.debugMap.containsKey(key));
// this.debugMap.put(key, 0);
// }
// }
// } else if (oldSize == 0) {
// if (oldVal != NotAvailable.NOT_AVAILABLE && oldVal != Token.REMOVED_PHASE1) {
// cache.getLogger().info("called updateSizeOnPut with oldVal=" + oldVal,
// new RuntimeException("STACK"));
// }
// // count it as a create since it is in memory again
// this.createCount.incrementAndGet();
// if (oldVal != Token.REMOVED_PHASE1) {
// Assert.assertTrue(this.debugMap.containsKey(key));
// Assert.assertTrue(oldSize == this.debugMap.get(key), "expected " + oldSize + "==" + this.debugMap.get(key));
// } else {
// Assert.assertTrue(!this.debugMap.containsKey(key));
// }
// this.debugMap.put(key, newSize);
// } else {
// this.putCount.incrementAndGet();
// Assert.assertTrue(oldSize == this.debugMap.get(key), "expected " + oldSize + "==" + this.debugMap.get(key));
// this.debugMap.put(key, newSize);
// }
updateBucket2Size(oldSize, newSize, SizeOp.UPDATE);
}
// /**
// * Return number of entries stored in memory on this bucket
// */
// private long getLiveCount() {
// return this.createCount.get() - this.removeCount.get() - this.invalidateCount.get()
// - (this.evictCount.get() - this.faultInCount.get());
// }
@Override
void updateSizeOnCreate(Object key, int newSize) {
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnCreate (" + this
// + " key=" + key
// + ") newSize = " + newSize
// + " cCount=" + this.createCount.get()
// + " rCount=" + this.removeCount.get()
// + " pCount=" + this.putCount.get()
// + " iCount=" + this.invalidateCount.get()
// + " eCount=" + this.evictCount.get()
// + " fCount=" + this.faultInCount.get()
// + " liveCount=" + getLiveCount()
// + " bSize=" + this.bytesInMemory.get());
// }
// if (newSize == 0 && newVal != Token.INVALID) {
// cache.getLogger().info("called updateSizeOnCreate with newVal=" + newVal,
// new RuntimeException("STACK"));
// } else {
// if (newSize != 0) {
// this.createCount.incrementAndGet();
// }
// Assert.assertTrue(!this.debugMap.containsKey(key));
// this.debugMap.put(key, newSize);
// }
this.partitionedRegion.getPrStats().incDataStoreEntryCount(1);
updateBucket2Size(0, newSize, SizeOp.CREATE);
}
@Override
void updateSizeOnRemove(Object key, int oldSize) {
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnRemove (" + this
// + " key=" + key
// + ") oldSize = " + oldSize
// + " oldObj=" + oldVal
// + " cCount=" + this.createCount.get()
// + " rCount=" + this.removeCount.get()
// + " pCount=" + this.putCount.get()
// + " iCount=" + this.invalidateCount.get()
// + " eCount=" + this.evictCount.get()
// + " fCount=" + this.faultInCount.get()
// + " liveCount=" + getLiveCount()
// + " bSize=" + this.bytesInMemory.get());
// }
// if (oldSize != 0) {
// this.removeCount.incrementAndGet();
// }
// Assert.assertTrue(this.debugMap.containsKey(key));
// Assert.assertTrue(oldSize == this.debugMap.get(key), "expected " + oldSize + "==" + this.debugMap.get(key));
// this.debugMap.remove(key);
this.partitionedRegion.getPrStats().incDataStoreEntryCount(-1);
updateBucket2Size(oldSize, 0, SizeOp.DESTROY);
freePoolMemory(oldSize + indexOverhead, true);
}
@Override
int updateSizeOnEvict(Object key, int oldSize) {
int newDiskSize = oldSize;
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnEvict (" + this
// + " key=" + key
// + ") oldSize=" + oldSize
// + " newDiskSize=" + newDiskSize
// + " cCount=" + this.createCount.get()
// + " rCount=" + this.removeCount.get()
// + " pCount=" + this.putCount.get()
// + " iCount=" + this.invalidateCount.get()
// + " eCount=" + this.evictCount.get()
// + " fCount=" + this.faultInCount.get()
// + " liveCount=" + getLiveCount()
// + " bSize=" + this.bytesInMemory.get());
// }
// if (oldSize != 0) {
// this.evictCount.incrementAndGet();
// }
// Assert.assertTrue(this.debugMap.containsKey(key));
// Assert.assertTrue(oldSize == this.debugMap.get(key), "expected " + oldSize + "==" + this.debugMap.get(key));
// this.debugMap.put(key, 0);
updateBucket2Size(oldSize, newDiskSize, SizeOp.EVICT);
return newDiskSize;
}
@Override
public void updateSizeOnFaultIn(Object key, int newMemSize, int oldDiskSize) {
// if (cache.getLogger().infoEnabled()) {
// cache.getLogger().info("updateSizeOnFaultIn (" + this
// + " key=" + key
// + ") oldDiskSize=" + oldDiskSize
// + " newSize=" + newMemSize
// + " cCount=" + this.createCount.get()
// + " rCount=" + this.removeCount.get()
// + " pCount=" + this.putCount.get()
// + " iCount=" + this.invalidateCount.get()
// + " eCount=" + this.evictCount.get()
// + " fCount=" + this.faultInCount.get()
// + " liveCount=" + getLiveCount()
// + " bSize=" + this.bytesInMemory.get());
// }
// if (newMemSize != 0) {
// this.faultInCount.incrementAndGet();
// }
// Assert.assertTrue(this.debugMap.containsKey(key));
// Assert.assertTrue(0 == this.debugMap.get(key), "expected " + 0 + "==" + this.debugMap.get(key));
// this.debugMap.put(key, newMemSize);
updateBucket2Size(oldDiskSize, newMemSize, SizeOp.FAULT_IN);
}
@Override
public void initializeStats(long numEntriesInVM, long numOverflowOnDisk,
long numOverflowBytesOnDisk) {
super.initializeStats(numEntriesInVM, numOverflowOnDisk, numOverflowBytesOnDisk);
incNumEntriesInVM(numEntriesInVM);
incNumOverflowOnDisk(numOverflowOnDisk);
incNumOverflowBytesOnDisk(numOverflowBytesOnDisk);
}
@Override
protected void setMemoryThresholdFlag(MemoryEvent event) {
Assert.assertTrue(false);
//Bucket regions are not registered with ResourceListener,
//and should not get this event
}
@Override
public void initialCriticalMembers(boolean localHeapIsCritical,
Set critialMembers) {
// The owner Partitioned Region handles critical threshold events
}
@Override
protected void closeCallbacksExceptListener() {
//closeCacheCallback(getCacheLoader()); - fix bug 40228 - do NOT close loader
closeCacheCallback(getCacheWriter());
closeCacheCallback(getEvictionController());
}
public long getSizeInMemory() {
return Math.max(this.bytesInMemory.get(), 0L);
}
public long getInProgressSize() {
return inProgressSize.get();
}
public void updateInProgressSize(long delta) {
inProgressSize.addAndGet(delta);
}
public long getTotalBytes() {
long result = this.bytesInMemory.get();
if(result == BUCKET_DESTROYED) {
return 0;
}
result += getNumOverflowBytesOnDisk();
return result;
}
public void preDestroyBucket() {
// cause abort of the GII thread
ImageState is = this.getImageState();
if (is != null) {
is.setClearRegionFlag(true, null);
}
}
@Override
protected boolean clearIndexes(IndexUpdater indexUpdater, boolean setIsDestroyed) {
BucketRegionIndexCleaner cleaner = new BucketRegionIndexCleaner(
!setIsDestroyed, this);
bucketRegionIndexCleaner.set(cleaner);
return false;
}
@Override
protected boolean isExplicitRegionDestroy(RegionEventImpl event) {
return event != null
&& Operation.REGION_LOCAL_DESTROY.equals(event.getOperation())
&& !PartitionedRegionDataStore.FOR_BUCKET_CLOSE.equals(event
.getRawCallbackArgument());
}
protected void invokePartitionListenerAfterBucketRemoved() {
PartitionListener[] partitionListeners = getPartitionedRegion().getPartitionListeners();
if (partitionListeners == null || partitionListeners.length == 0) {
return;
}
for (int i = 0; i < partitionListeners.length; i++) {
PartitionListener listener = partitionListeners[i];
if (listener != null) {
listener.afterBucketRemoved(getId(), keySet());
}
}
}
protected void invokePartitionListenerAfterBucketCreated() {
PartitionListener[] partitionListeners = getPartitionedRegion().getPartitionListeners();
if (partitionListeners == null || partitionListeners.length == 0) {
return;
}
for (int i = 0; i < partitionListeners.length; i++) {
PartitionListener listener = partitionListeners[i];
if (listener != null) {
listener.afterBucketCreated(getId(), keySet());
}
}
}
enum SizeOp {
UPDATE, CREATE, DESTROY, EVICT, FAULT_IN;
int computeMemoryDelta(int oldSize, int newSize) {
switch (this) {
case CREATE:
return newSize;
case DESTROY:
return - oldSize;
case UPDATE:
return newSize - oldSize;
case EVICT:
return - oldSize;
case FAULT_IN:
return newSize;
default:
throw new AssertionError("unhandled sizeOp: " + this);
}
}
};
/**
* Updates the bucket size.
*/
void updateBucket2Size(int oldSize, int newSize,
SizeOp op) {
// now done from AbstractRegionEntry._setValue by a direct call to
// updateBucketMemoryStats
/*
final int memoryDelta = op.computeMemoryDelta(oldSize, newSize);
if (memoryDelta == 0) return;
// cache.getLogger().fine("updateBucketSize(): delta " + delta
// + " b2size[" + bucketId + "]:" + b2size[(int) bucketId]
// + " bytesInUse: "+ bytesInUse
// + " oldObjBytes: " + oldSize
// + " newObjBytes: "+ newSize, new Exception());
// do the bigger one first to keep the sum > 0
updateBucketMemoryStats(memoryDelta);
*/
}
@Override
public void updateMemoryStats(final Object oldValue, final Object newValue) {
if (newValue != oldValue) {
int oldValueSize = calcMemSize(oldValue);
int newValueSize = calcMemSize(newValue);
updateBucketMemoryStats(newValueSize - oldValueSize);
}
}
private void updateBucketMemoryStats(final int memoryDelta) {
if (memoryDelta != 0) {
final long bSize = bytesInMemory.compareAddAndGet(BUCKET_DESTROYED, memoryDelta);
// debugging output for #40116
// if (bSize <= 0) {
// cache.getLogger().info("DEBUG: bSize=" + bSize + " delta=" + memoryDelta
// + " " + System.identityHashCode(this),
// ((bSize == 0 || bSize == memoryDelta)? new Exception("stack trace") : null));
// }
if(bSize == BUCKET_DESTROYED) {
return;
}
if (bSize < 0 && getCancelCriterion().cancelInProgress() == null) {
// cache.getLogger().info("DEBUG: death " + System.identityHashCode(this));
throw new InternalGemFireError("Bucket " + this + " size (" +
bSize + ") negative after applying delta of " + memoryDelta);
}
final PartitionedRegionDataStore prDS = this.partitionedRegion.getDataStore();
prDS.updateMemoryStats(memoryDelta);
//cache.getLogger().fine("DEBUG updateBucketMemoryStats delta=" + memoryDelta + " newSize=" + bytesInMemory.get(), new RuntimeException("STACK"));
}
}
public static BucketRegionIndexCleaner getIndexCleaner() {
BucketRegionIndexCleaner cleaner = bucketRegionIndexCleaner.get();
bucketRegionIndexCleaner.set(null);
return cleaner;
}
/**
* Returns the current number of entries whose value has been
* overflowed to disk by this bucket.This value will decrease when a value is
* faulted in.
*/
public long getNumOverflowOnDisk() {
return this.numOverflowOnDisk.get();
}
public long getNumOverflowBytesOnDisk() {
return this.numOverflowBytesOnDisk.get();
}
/**
* Returns the current number of entries whose value resides in the
* VM for this bucket. This value will decrease when the entry is overflowed to
* disk.
*/
public long getNumEntriesInVM() {
return this.numEntriesInVM.get();
}
/**
* Increments the current number of entries whose value has been
* overflowed to disk by this bucket, by a given amount.
*/
void incNumOverflowOnDisk(long delta) {
this.numOverflowOnDisk.addAndGet(delta);
}
void incNumOverflowBytesOnDisk(long delta) {
if (delta == 0) return;
this.numOverflowBytesOnDisk.addAndGet(delta);
// The following could be reenabled at a future time.
// I deadcoded for now to make sure I didn't have it break
// the last 6.5 regression.
// It is possible that numOverflowBytesOnDisk might go negative
// for a short period of time if a decrement ever happens before
// its corresponding increment.
// if (res < 0) {
// throw new IllegalStateException("numOverflowBytesOnDisk < 0 " + res);
// }
}
/**
* Increments the current number of entries whose value has been
* overflowed to disk by this bucket,by a given amount.
*/
void incNumEntriesInVM(long delta) {
this.numEntriesInVM.addAndGet(delta);
}
public void incEvictions(long delta ) {
this.evictions.getAndAdd(delta);
}
public long getEvictions( ) {
return this.evictions.get();
}
@Override
protected boolean isMemoryThresholdReachedForLoad() {
return getBucketAdvisor().getProxyBucketRegion().isBucketSick();
}
public int getSizeForEviction() {
EvictionAttributes ea = this.getAttributes().getEvictionAttributes();
if (ea == null)
return 0;
EvictionAlgorithm algo = ea.getAlgorithm();
if (!algo.isLRUHeap())
return 0;
EvictionAction action = ea.getAction();
int size = action.isLocalDestroy() ? this.getRegionMap().sizeInVM() : (int)this
.getNumEntriesInVM();
return size;
}
@Override
public HashMap getDestroyedSubregionSerialNumbers() {
return new HashMap(0);
}
@Override
public FilterProfile getFilterProfile(){
return this.partitionedRegion.getFilterProfile();
}
@Override
protected void generateLocalFilterRouting(InternalCacheEvent event) {
if (event.getLocalFilterInfo() == null) {
super.generateLocalFilterRouting(event);
}
}
public void beforeAcquiringPrimaryState() {
try {
createHoplogOrganizer();
} catch (IOException e) {
// 48990: when HDFS was down, gemfirexd should still start normally
getPartitionedRegion().getLogWriterI18n().warning(LocalizedStrings.HOPLOG_NOT_STARTED_YET, e);
} catch(Throwable e) {
SystemFailure.checkThrowable(e);
//49333 - no matter what, we should elect a primary.
getPartitionedRegion().getLogWriterI18n().error(LocalizedStrings.LocalRegion_UNEXPECTED_EXCEPTION, e);
}
}
public HoplogOrganizer> createHoplogOrganizer() throws IOException {
if (getPartitionedRegion().isHDFSRegion()) {
HoplogOrganizer> organizer = hoplog.get();
if (organizer != null) {
// hoplog is recreated by anther thread
return organizer;
}
HoplogOrganizer hdfs = hoplog.getAndSet(getPartitionedRegion().hdfsManager.create(getId()));
assert hdfs == null;
return hoplog.get();
} else {
return null;
}
}
public void afterAcquiringPrimaryState() {
}
/**
* Invoked when a primary bucket is demoted.
*/
public void beforeReleasingPrimaryLockDuringDemotion() {
releaseHoplogOrganizer();
}
protected void releaseHoplogOrganizer() {
// release resources during a clean transition
HoplogOrganizer hdfs = hoplog.getAndSet(null);
if (hdfs != null) {
getPartitionedRegion().hdfsManager.close(getId());
}
}
public HoplogOrganizer> getHoplogOrganizer() throws HDFSIOException {
HoplogOrganizer> organizer = hoplog.get();
if (organizer == null) {
synchronized (getBucketAdvisor()) {
checkForPrimary();
try {
organizer = createHoplogOrganizer();
} catch (IOException e) {
throw new HDFSIOException("Failed to create Hoplog organizer due to ", e);
}
if (organizer == null) {
throw new HDFSIOException("Hoplog organizer is not available for " + this);
}
}
}
return organizer;
}
@Override
public RegionAttributes getAttributes() {
return this;
}
@Override
public void hdfsCalled(Object key) {
this.partitionedRegion.hdfsCalled(key);
}
@Override
protected void clearHDFSData() {
//clear the HDFS data if present
if (getPartitionedRegion().isHDFSReadWriteRegion()) {
// Clear the queue
ConcurrentParallelGatewaySenderQueue q = getHDFSQueue();
if (q == null) return;
q.clear(getPartitionedRegion(), this.getId());
HoplogOrganizer organizer = hoplog.get();
if (organizer != null) {
try {
organizer.clear();
} catch (IOException e) {
throw new GemFireIOException(LocalizedStrings.HOPLOG_UNABLE_TO_DELETE_HDFS_DATA.toLocalizedString(), e);
}
}
}
}
public EvictionCriteria getEvictionCriteria() {
return this.partitionedRegion.getEvictionCriteria();
}
public CustomEvictionAttributes getCustomEvictionAttributes() {
return this.partitionedRegion.getCustomEvictionAttributes();
}
/**
* @return true if the evict destroy was done; false if it was not needed
*/
public boolean customEvictDestroy(Object key)
{
checkReadiness();
final EntryEventImpl event =
generateCustomEvictDestroyEvent(key);
event.setCustomEviction(true);
boolean locked = false;
try {
locked = beginLocalWrite(event);
return mapDestroy(event,
false, // cacheWrite
true, // isEviction
null); // expectedOldValue
}
catch (CacheWriterException error) {
throw new Error(LocalizedStrings.LocalRegion_CACHE_WRITER_SHOULD_NOT_HAVE_BEEN_CALLED_FOR_EVICTDESTROY.toLocalizedString(), error);
}
catch (TimeoutException anotherError) {
throw new Error(LocalizedStrings.LocalRegion_NO_DISTRIBUTED_LOCK_SHOULD_HAVE_BEEN_ATTEMPTED_FOR_EVICTDESTROY.toLocalizedString(), anotherError);
}
catch (EntryNotFoundException yetAnotherError) {
throw new Error(LocalizedStrings.LocalRegion_ENTRYNOTFOUNDEXCEPTION_SHOULD_BE_MASKED_FOR_EVICTDESTROY.toLocalizedString(), yetAnotherError);
} finally {
if (locked) {
endLocalWrite(event);
}
event.release();
}
}
public boolean areSecondariesPingable() {
Set hostingservers = this.partitionedRegion.getRegionAdvisor()
.getBucketOwners(this.getId());
hostingservers.remove(cache.getDistributedSystem().getDistributedMember());
if (cache.getLoggerI18n().fineEnabled())
cache.getLoggerI18n().fine("Pinging secondaries of bucket " + this.getId() + " on servers " + hostingservers);
if (hostingservers.size() == 0)
return true;
return ServerPingMessage.send(cache, hostingservers);
}
@Override
public boolean isRowBuffer() {
return getPartitionedRegion().isRowBuffer();
}
@Override
public boolean isInternalColumnTable() {
return getPartitionedRegion().isInternalColumnTable();
}
@Override
public boolean isSnapshotEnabledRegion() {
// concurrency checks is by default true in column table
return isInternalColumnTable() || isRowBuffer() || super.isSnapshotEnabledRegion();
}
}