blob: 9562c96a793b5755764b34d677c63ce6a6bd8372 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.
*/
package org.apache.geode.internal.cache.versions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.apache.geode.DataSerializable;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.internal.Assert;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.logging.log4j.LogMarker;
import org.apache.geode.logging.internal.log4j.api.LogService;
/**
* RegionVersionHolders are part of a RegionVersionVector. A RVH holds the current version for a
* member and a list of exceptions, which are holes in the list of versions received from that
* member.
*
* RegionVersionHolders should be modified under synchronization on the holder.
*
* Starting in 7.0.1 the holder has a BitSet that records the most recent versions. The variable
* bitSetVersion corresponds to bit zero, and subsequent bits represent bitSetVersion+1, +2, etc.
* The method mergeBitSet() should be used to dump the BitSet's exceptions into the regular
* exceptions list prior to performing operations like exceptions- comparisons or dominance checks.
*
* Starting in 8.0, the holder introduced a special exception to describe following use case of
* unfinished operation: Operation R4 and R5 are applied locally, but never distributed to P. So P's
* RVV for R is still 3. After R GIIed from P, R's RVV becomes R5(3-6), i.e. Exception's nextVersion
* is currentVersion+1.
*
*/
public class RegionVersionHolder<T> implements Cloneable, DataSerializable {
private static final Logger logger = LogService.getLogger();
@Immutable
private static final List<RVVException> EMPTY_EXCEPTIONS = Collections.emptyList();
long version = -1; // received version
transient T id;
private List<RVVException> exceptions;
boolean isDepartedMember;
// This flag is used to to determine if region sync is needed when receiving region sync requests
// from other members hosting the region.
//
// It is set to true when region sync is being scheduled when triggered by the
// member departed event, if the lost member is the holding member by this holder.
// It can also be set to true, if region sync is not scheduled (this member joins the
// cluster after the lost member departed event has occurred) but this member receives
// request for region sync from other existing members. If this is the case, this member
// will send region sync request to other existing members hosting the region.
//
// The flag will be always set to true once region sync is scheduled or done for the holding
// member. If the holding member by the holder is lost multiples times and this member is
// never lost, the timed task would schedule region sync for the lost member (bypassing this
// condition check). If this member is also lost, when this member is restarted this
// flag will be initialized as false.
private transient boolean regionSynchronizeScheduledOrDone;
// non final for tests
@MutableForTesting
public static int BIT_SET_WIDTH = 64 * 16; // should be a multiple of 4 64-bit longs
private long bitSetVersion = 1;
private BitSet bitSet;
/**
* This contructor should only be used for cloning a RegionVersionHolder or initializing and
* invalid version holder (with version -1)
*
*/
public RegionVersionHolder(long ver) {
this.version = ver;
}
public RegionVersionHolder(T id) {
this.id = id;
this.version = 0;
this.bitSetVersion = 1;
this.bitSet = new BitSet(RegionVersionHolder.BIT_SET_WIDTH);
}
public RegionVersionHolder(DataInput in) throws IOException {
fromData(in);
}
public synchronized long getVersion() {
RVVException e = null;
List<RVVException> exs = getExceptions();
if (!exs.isEmpty()) {
e = exs.get(0);
}
if (isSpecialException(e, this)) {
return e.getHighestReceivedVersion();
} else {
return this.version;
}
}
private synchronized RVVException getSpecialException() {
RVVException e = null;
if (this.exceptions != null && !this.exceptions.isEmpty()) {
e = this.exceptions.get(0);
}
if (isSpecialException(e, this)) {
return e;
} else {
return null;
}
}
public long getBitSetVersionForTesting() {
return this.bitSetVersion;
}
public BitSet getBitSetForTesting() {
return this.bitSet;
}
private synchronized List<RVVException> getExceptions() {
mergeBitSet();
if (this.exceptions != null) {
return this.exceptions;
} else {
return EMPTY_EXCEPTIONS;
}
}
public synchronized List<RVVException> getExceptionForTest() {
return getExceptions();
}
public synchronized int getExceptionCount() {
return getExceptions().size();
}
public synchronized String exceptionsToString() {
return getExceptions().toString();
}
/**
* Should only be called as part of cloning a RegionVersionHolder
*/
void setVersion(long ver) {
this.version = ver;
}
@Override
public synchronized RegionVersionHolder<T> clone() {
RegionVersionHolder<T> clone = new RegionVersionHolder<T>(this.version);
clone.id = this.id;
clone.isDepartedMember = this.isDepartedMember;
if (this.exceptions != null) {
clone.exceptions = new LinkedList<RVVException>();
for (RVVException e : this.exceptions) {
clone.exceptions.add(e.clone());
}
}
if (this.bitSet != null) {
clone.bitSet = (BitSet) this.bitSet.clone();
clone.bitSetVersion = this.bitSetVersion;
clone.mergeBitSet();
}
return clone;
}
@Override
public synchronized String toString() {
// mergeBitSet();
StringBuilder sb = new StringBuilder();
sb.append("{rv").append(this.version).append(" bsv").append(this.bitSetVersion).append(" bs=[");
if (this.bitSet != null) {
int i = this.bitSet.nextSetBit(0);
if (i >= 0) {
sb.append("0");
for (i = this.bitSet.nextSetBit(1); i > 0; i = this.bitSet.nextSetBit(i + 1)) {
sb.append(',').append(i);
}
}
}
sb.append(']');
if (this.exceptions != null && !this.exceptions.isEmpty()) {
sb.append(this.exceptions.toString());
}
sb.append("}");
return sb.toString();
}
/** add a version that is older than this.bitSetVersion */
private void addOlderVersion(long missingVersion) {
// exceptions iterate in reverse order on their previousVersion variable
if (this.exceptions == null) {
return;
}
int i = 0;
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext();) {
RVVException e = it.next();
if (e.nextVersion <= missingVersion) {
return; // there is no RVVException for this version
}
if (e.previousVersion < missingVersion) {
String fine = null;
if (logger.isTraceEnabled(LogMarker.RVV_VERBOSE)) {
fine = e.toString();
}
e.add(missingVersion);
if (e.isFilled()) {
if (fine != null) {
logger.trace(LogMarker.RVV_VERBOSE, "Filled exception {}", fine);
}
it.remove();
} else if (e.shouldChangeForm()) {
this.exceptions.set(i, e.changeForm());
}
if (this.exceptions.isEmpty()) {
this.exceptions = null;
}
return;
}
i++;
}
}
void flushBitSetDuringRecording(long version) {
if (this.bitSetVersion + BIT_SET_WIDTH - 1 >= version) {
return; // it fits in this bitset
}
int bitCountToFlush = BIT_SET_WIDTH * 3 / 4;
// We can only flush up to the last set bit because
// the exceptions list includes a "next version" that indicates a received version.
bitCountToFlush = bitSet.previousSetBit(bitCountToFlush);
if (logger.isTraceEnabled(LogMarker.RVV_VERBOSE)) {
logger.trace(LogMarker.RVV_VERBOSE, "flushing RVV bitset bitSetVersion={}; bits={}",
this.bitSetVersion, this.bitSet);
}
// see if we can shift part of the bits so that exceptions in the recent bits can
// be kept in the bitset and later filled without having to create real exception objects
if (bitCountToFlush == -1 || version >= this.bitSetVersion + BIT_SET_WIDTH + bitCountToFlush) {
// nope - flush the whole bitset
addBitSetExceptions(version);
} else {
// yes - flush the lower part. We can only flush up to the last set bit because
// the exceptions list includes a "next version" that indicates a received version.
addBitSetExceptions(this.bitSetVersion + bitCountToFlush);
}
if (logger.isTraceEnabled(LogMarker.RVV_VERBOSE)) {
logger.trace(LogMarker.RVV_VERBOSE, "After flushing bitSetVersion={}; bits={}",
this.bitSetVersion, this.bitSet);
}
}
/** merge bit-set exceptions into the regular exceptions list */
private synchronized void mergeBitSet() {
if (this.bitSet != null && this.bitSetVersion < this.version) {
addBitSetExceptions(this.version);
}
}
/**
* Add exceptions from the BitSet array to the exceptions list. Assumes that the BitSet[0]
* corresponds to this.bitSetVersion. This scans the bitset looking for gaps that are recorded as
* RVV exceptions. The scan terminates at numBits or when the last set bit is found. The bitSet is
* adjusted and a new bitSetVersion is established.
*
* @param newVersion the desired new bitSetVersion, which may be > the max representable in the
* bitset. This should *always* be a version that has been received, because this
* method may need to create an exception up to this version, and the existance of an
* exception implies that the final version was received.
*
*
*/
private void addBitSetExceptions(long newVersion) {
if (newVersion <= bitSetVersion) {
return;
}
// Add all of the exceptions that should be flushed from the bitset as real exceptions
Iterator<RVVException> exceptionIterator =
new BitSetExceptionIterator(bitSet, bitSetVersion, newVersion);
while (exceptionIterator.hasNext()) {
addException(exceptionIterator.next());
}
// Move the data in the bitset forward to reflect the new version
if (newVersion > bitSetVersion + bitSet.size()) {
// Optimization - if the new version is past the end of the bitset, just clear the bitset
bitSet.clear();
} else {
// Otherwise slide the bitset over to the new offset
int offsetIncrease = (int) (newVersion - bitSetVersion);
bitSet = bitSet.get(offsetIncrease, bitSet.size());
}
// Move the bitset version
bitSetVersion = newVersion;
}
synchronized void recordVersion(long version) {
if (this.bitSet != null) {
recordVersionWithBitSet(version);
} else {
recordVersionWithoutBitSet(version);
}
}
private void recordVersionWithoutBitSet(long version) {
if ((version - this.version) > 1) {
this.addException(this.version, version);
logRecordVersion(version);
this.version = version;
return;
}
if (this.version > version) {
this.addOlderVersion(version);
}
this.version = Math.max(this.version, version);
}
private void recordVersionWithBitSet(long version) {
flushBitSetDuringRecording(version);
if (this.version == version) {
if (version >= this.bitSetVersion) {
setVersionInBitSet(version);
}
this.addOlderVersion(version);
return;
}
if (version >= this.bitSetVersion) {
if (this.getSpecialException() != null) {
this.addOlderVersion(version);
}
setVersionInBitSet(version);
this.version = Math.max(this.version, version);
return;
}
this.addOlderVersion(version);
this.version = Math.max(this.version, version);
}
private void setVersionInBitSet(long version) {
long bitToSet = version - this.bitSetVersion;
if (bitToSet > BIT_SET_WIDTH) {
Assert.fail("Trying to set a bit larger than the size of the bitset " + bitToSet);
}
this.bitSet.set(Math.toIntExact(bitToSet));
}
private void logRecordVersion(long version) {
if (logger.isTraceEnabled(LogMarker.RVV_VERBOSE)) {
logger.trace(LogMarker.RVV_VERBOSE, "Added rvv exception e<rv{} - rv{}>", this.version,
version);
}
}
/**
* Add an exception that is older than this.bitSetVersion.
*/
synchronized void addException(final long previousVersion, final long nextVersion) {
RVVException newException = RVVException.createException(previousVersion, nextVersion);
addException(newException);
}
private void addException(RVVException newException) {
if (this.exceptions == null) {
this.exceptions = new LinkedList<RVVException>();
}
int i = 0;
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext(); i++) {
RVVException e = it.next();
if (newException.previousVersion >= e.nextVersion) {
RVVException except = newException;
this.exceptions.add(i, except);
return;
}
}
this.exceptions.add(newException);
}
synchronized void removeExceptionsOlderThan(long v) {
mergeBitSet();
if (this.exceptions != null) {
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext();) {
RVVException e = it.next();
if (e.nextVersion <= v) {
it.remove();
}
}
if (this.exceptions.isEmpty()) {
this.exceptions = null;
}
}
}
/**
* Initialize this version holder from another version holder This is called during GII.
*
* It's more likely that the other holder has seen most of the versions, and this version holder
* only has a few updates that happened since the GII started. So we apply our seen versions to
* the other version holder and then initialize this version holder from the other version holder.
*/
public synchronized void initializeFrom(RegionVersionHolder<T> source) {
// Make sure the bitsets are merged in both the source
// and this vector
mergeBitSet();
RegionVersionHolder<T> other = source.clone();
other.mergeBitSet();
// Get a copy of the local version and exceptions
long myVersion = this.version;
// initialize our version and exceptions to match the others
this.exceptions = other.exceptions;
this.version = other.version;
// Now if this.version/exceptions overlap with myVersion/myExceptions, use this'
// The only case needs special handling is: if myVersion is newer than this.version,
// should create an exception (this.version+1, myversion) and set this.version=myversion
if (myVersion > this.version) {
RVVException e = RVVException.createException(this.version, myVersion + 1);
// add special exception
if (this.exceptions == null) {
this.exceptions = new LinkedList<RVVException>();
}
int i = 0;
for (RVVException exception : this.exceptions) {
if (e.compareTo(exception) >= 0) {
break;
}
i++;
}
this.exceptions.add(i, e);
this.version = myVersion;
}
// Initialize the bit set to be empty. Merge bit set should
// have already done this, but just to be sure.
if (this.bitSet != null) {
this.bitSetVersion = this.version;
// Make sure the bit set is empty except for the first, bit, indicating
// that the version has been received.
this.bitSet.set(0);
}
}
/**
* initialize a holder that was cloned from another holder so it is ready for use in a live vector
*/
void makeReadyForRecording() {
if (this.bitSet == null) {
this.bitSet = new BitSet(BIT_SET_WIDTH);
this.bitSetVersion = this.version;
this.bitSet.set(0);
}
}
/**
* returns true if this version holder has seen the given version number
*/
synchronized boolean contains(long v) {
if (v > getVersion()) {
return false;
} else {
if (this.bitSet != null && v >= this.bitSetVersion) {
return this.bitSet.get((int) (v - this.bitSetVersion));
}
if (this.exceptions == null) {
return true;
}
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext();) {
RVVException e = it.next();
if (e.nextVersion <= v) {
return true; // there is no RVVException for this version
}
if (e.previousVersion < v) {
return e.contains(v);
}
}
return true;
}
}
/**
* Returns true if this version hold has an exception in the exception list for the given version
* number.
*
* This differs from contains because it returns true if v is greater than the last seen version
* for this holder.
*/
synchronized boolean hasExceptionFor(long v) {
if (this.bitSet != null && v >= this.bitSetVersion) {
if (v > this.bitSetVersion + this.bitSet.length()) {
return false;
}
return this.bitSet.get((int) (v - this.bitSetVersion));
}
if (this.exceptions == null) {
return false;
}
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext();) {
RVVException e = it.next();
if (e.nextVersion <= v) {
return false; // there is no RVVException for this version
}
if (e.previousVersion < v) {
return !e.contains(v);
}
}
return false;
}
public boolean dominates(RegionVersionHolder<T> other) {
return !other.isNewerThanOrCanFillExceptionsFor(this);
}
public boolean isSpecialException(RVVException e, RegionVersionHolder holder) {
// deltaGII introduced a special exception, i.e. the hone is not in the middle, but at the end
// For example, P was at P3, operation P4 is on-going and identified as unfinished operation.
// The next operation from P should be P5, but P's currentVersion() should be 3. In holder,
// it's described as P3(2-4), i.e. exception.nextVersion == holder.version + 1
return (e != null && e.nextVersion == holder.version + 1);
}
/** returns true if this holder has seen versions that the other holder hasn't */
public synchronized boolean isNewerThanOrCanFillExceptionsFor(RegionVersionHolder<T> source) {
if (source == null || getVersion() > source.getVersion()) {
return true;
}
// Prevent synhronization issues if other is a live version vector.
RegionVersionHolder<T> other = source.clone();
// since the exception sets are sorted with most recent ones first
// we can make one pass over both sets to see if there are overlapping
// exceptions or exceptions I don't have that the other does
mergeBitSet(); // dump the bit-set exceptions into the regular exceptions list
other.mergeBitSet();
List<RVVException> mine = canonicalExceptions(this.exceptions);
Iterator<RVVException> myIterator = mine.iterator();
List<RVVException> others = canonicalExceptions(other.exceptions);
Iterator<RVVException> otherIterator = others.iterator();
// System.out.println("comparing " + mine + " with " + others);
RVVException myException = myIterator.hasNext() ? myIterator.next() : null;
RVVException otherException = otherIterator.hasNext() ? otherIterator.next() : null;
// I can't fill exceptions that are newer than anything I've seen, so skip them
while ((otherException != null && otherException.previousVersion > this.version)
|| isSpecialException(otherException, other)) {
otherException = otherIterator.hasNext() ? otherIterator.next() : null;
}
while (otherException != null) {
// System.out.println("comparing " + myException + " with " + otherException);
if (myException == null) {
return true;
}
if (isSpecialException(myException, this)) {
// skip special exception
myException = myIterator.hasNext() ? myIterator.next() : null;
continue;
}
if (isSpecialException(otherException, other)) {
// skip special exception
otherException = otherIterator.hasNext() ? otherIterator.next() : null;
continue;
}
if (myException.previousVersion >= otherException.nextVersion) {
// |____| my exception
// |____| other exception
// my exception is newer than the other exception, so get the next one in the sorted list
myException = myIterator.hasNext() ? myIterator.next() : null;
continue;
}
if (otherException.previousVersion >= myException.nextVersion) {
// |____| my exception
// |____| other exception
// my exception is older than the other exception, so I have seen changes
// it has not
return true;
}
if ((myException.previousVersion == otherException.previousVersion)
&& (myException.nextVersion == otherException.nextVersion)) {
// |____| my exception
// |____| -- other exception
// If the exceptions are identical we can skip both of them and
// go to the next pair
myException = myIterator.hasNext() ? myIterator.next() : null;
otherException = otherIterator.hasNext() ? otherIterator.next() : null;
continue;
}
// There is some overlap between my exception and the other exception.
//
// |_________________| my exception
// |____| \
// |____|* \ the other exception is one of
// |____| / these
// |_____________________| /
//
// Unless my exception completely contains the other exception (*)
// I have seen changes the other hasn't
if ((otherException.previousVersion < myException.previousVersion)
|| (myException.nextVersion < otherException.nextVersion)) {
return true;
}
// My exception completely contains the other exception and I have not
// received any thing within its exception's range that it has not also seen
otherException = otherIterator.hasNext() ? otherIterator.next() : null;
}
// System.out.println("Done iterating and returning false");
return false;
}
/*
* (non-Javadoc)
*
* @see org.apache.geode.DataSerializable#toData(java.io.DataOutput)
*
* Version Holders serialized to disk, so if the serialization format of version holder changes,
* we need to upgrade our persistence format.
*/
@Override
public synchronized void toData(DataOutput out) throws IOException {
mergeBitSet();
InternalDataSerializer.writeUnsignedVL(this.version, out);
int size = (this.exceptions == null) ? 0 : this.exceptions.size();
InternalDataSerializer.writeUnsignedVL(size, out);
out.writeBoolean(this.isDepartedMember);
if (size > 0) {
for (RVVException e : this.exceptions) {
InternalDataSerializer.invokeToData(e, out);
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.geode.DataSerializable#fromData(java.io.DataInput)
*/
@Override
public void fromData(DataInput in) throws IOException {
this.version = InternalDataSerializer.readUnsignedVL(in);
int size = (int) InternalDataSerializer.readUnsignedVL(in);
this.isDepartedMember = in.readBoolean();
if (size > 0) {
this.exceptions = new LinkedList<RVVException>();
for (int i = 0; i < size; i++) {
RVVException e = RVVException.createException(in);
this.exceptions.add(e);
}
}
}
/*
* Warning: this hashcode uses mutable state and is only good for as long as the holder is not
* modified. It was added for unit testing.
*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public synchronized int hashCode() {
mergeBitSet();
final int prime = 31;
int result = 1;
result = prime * result + (int) version;
result = prime * result + (int) (version >> 32);
result = prime * result + canonicalExceptions(exceptions).hashCode();
return result;
}
// special exception will be kept in clone, but sometime we need to remove it for comparing
// 2 RegionVersionHolders are actually the same
void removeSpecialException() {
if (this.exceptions != null && !this.exceptions.isEmpty()) {
for (Iterator<RVVException> it = this.exceptions.iterator(); it.hasNext();) {
RVVException e = it.next();
if (isSpecialException(e, this)) {
it.remove();
}
}
if (this.exceptions.isEmpty()) {
this.exceptions = null;
}
}
}
/**
* For test purposes only. Two RVVs that have effectively same exceptions may represent the
* exceptions differently. This method will test to see if the exception lists are effectively the
* same, regardless of representation.
*/
public synchronized boolean sameAs(RegionVersionHolder<T> other) {
mergeBitSet();
if (getVersion() != other.getVersion()) {
return false;
}
RegionVersionHolder<T> vh1 = this.clone();
RegionVersionHolder<T> vh2 = other.clone();
vh1.removeSpecialException();
vh2.removeSpecialException();
if (vh1.exceptions == null || vh1.exceptions.isEmpty()) {
if (vh2.exceptions != null && !vh2.exceptions.isEmpty()) {
return false;
}
} else {
List<RVVException> e1 = canonicalExceptions(vh1.exceptions);
List<RVVException> e2 = canonicalExceptions(vh2.exceptions);
Iterator<RVVException> it1 = e1.iterator();
Iterator<RVVException> it2 = e2.iterator();
while (it1.hasNext() && it2.hasNext()) {
if (!it1.next().sameAs(it2.next())) {
return false;
}
}
return (!it1.hasNext() && !it2.hasNext());
}
return true;
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof RegionVersionHolder)) {
return false;
}
return sameAs((RegionVersionHolder) obj);
}
/**
* Canonicalize an ordered set of exceptions. In the canonical form, none of the RVVExceptions
* have any received versions.
*
* @return The canonicalized set of exceptions.
*/
protected static List<RVVException> canonicalExceptions(List<RVVException> exceptions) {
LinkedList<RVVException> canon = new LinkedList<RVVException>();
if (exceptions != null) {
// Iterate through the set of exceptions
for (RVVException exception : exceptions) {
if (exception.isEmpty()) {
canon.add(exception);
} else {
long previous = exception.nextVersion;
// Iterate through the set of received versions for this exception
for (RVVException.ReceivedVersionsReverseIterator it =
exception.receivedVersionsReverseIterator(); it.hasNext();) {
long received = it.next();
// If we find a gap between the previous received version and the
// next received version, add an exception.
if (received != previous - 1) {
canon.add(RVVException.createException(received, previous));
}
// move the previous reference
previous = received;
}
// if there is a gap between the first received version and the previous
// version, add an exception
// this also handles the case where the RVV has no received versions,
// because previous==exception.nextVersion in that case.
if (exception.previousVersion != previous - 1) {
canon.add(RVVException.createException(exception.previousVersion, previous));
}
}
}
}
return canon;
}
private synchronized boolean isRegionSynchronizeScheduledOrDone() {
return regionSynchronizeScheduledOrDone;
}
public synchronized void setRegionSynchronizeScheduled() {
regionSynchronizeScheduledOrDone = true;
}
/**
* Check to see if regionSynchronizeScheduledOrDone is set to true. If it is not,
* the regionSynchronizeScheduledOrDone variable is set to true and returns true.
* If it is already set to true, do nothing and returns false.
*/
public synchronized boolean setRegionSynchronizeScheduledOrDoneIfNot() {
if (!isRegionSynchronizeScheduledOrDone()) {
regionSynchronizeScheduledOrDone = true;
return true;
}
return false;
}
}