Here is a GemFire function I wrote that will clear out a region. This code could be a lot shorter if you threw out the fluff and extra features we never ended up using. Also in hindsight I didn't need to "synchronize" the list of regions being cleared because the odds of two people calling the function in the same microsecond are virtually zero.
We use it in both GemFire 7 and GemFire 8 clusters. Once you put this in a jar and install it, you can call this function from gfsh to clear a region.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import com.gemstone.gemfire.LogWriter;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Declarable;
import com.gemstone.gemfire.cache.EntryNotFoundException;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.execute.Function;
import com.gemstone.gemfire.cache.execute.FunctionContext;
import com.gemstone.gemfire.cache.execute.RegionFunctionContext;
import com.gemstone.gemfire.cache.partition.PartitionRegionHelper;
import com.gemstone.gemfire.distributed.DistributedSystem;
public class ClearRegionFunction implements Function, Declarable {
private static final long serialVersionUID = 1L;
private static LogWriter log;
private static List<String> clearingRegionList = new ArrayList<String>();
static {
DistributedSystem ds = CacheFactory.getAnyInstance().getDistributedSystem();
log = ds.getLogWriter();
}
@Override
public void execute(FunctionContext fc) {
RegionFunctionContext rfc = (RegionFunctionContext) fc;
Region region = rfc.getDataSet();
String regionName = region.getName();
//If passed a flag of "true", that says to simulate the clear, but don't actually clear.
//This is used to test if a clear is already in progress, in which case we'd return false.
Boolean simulate = (Boolean)rfc.getArguments();
log.fine("Argument passed = " + simulate);
if (simulate == null) simulate = false;
if (simulate) {
rfc.getResultSender().lastResult( ! clearingRegionList.contains(regionName));
return;
}
log.warning("Clearing region: " + regionName); // Used "warning" because clearing a region is serious.
try {
// Protect against the same region being cleared twice at the same time.
synchronized (clearingRegionList) {
if (clearingRegionList.contains(regionName)) {
log.error("Clear of region " + regionName + " is already in progress. Aborting.");
// Let the client know we ignored their "clear" request.
rfc.getResultSender().lastResult(false);
return;
}
clearingRegionList.add(regionName);
}
if (!PartitionRegionHelper.isPartitionedRegion(region)) {
region.clear();
rfc.getResultSender().lastResult(true);
} else {
// We are going to clear the region in a partitioned manner, each node only clearing
// the data in it's own node. So we need to get the "local" region for the node.
Region localRegion = PartitionRegionHelper.getLocalDataForContext(rfc);
// Beware, this keySet() is a reference to the actual LIVE key set in memory. So
// we need to clone the set of keys we want to delete, otherwise we'll be looping
// through a live list and potentially deleting items that were added after the
// delete started.
List keyList = new ArrayList(localRegion.keySet());
// Once we have the keys, go ahead and set the lastResult to "true" to
// unblock the caller, because this could take a while. (The caller doesn't actually
// unblock until ALL nodes have returned "true".)
rfc.getResultSender().lastResult(true);
int regionSize = keyList.size();
log.info("Region " + regionName + " has " + regionSize + " entries to clear.");
int count = 0;
for (Object key : keyList) {
//The "remove" method returns the object removed. This is bad because it (sometimes?) causes
//GemFire to try and deserialize the object, and that fails because we don't have the class on
//our server classpath. But if we invalidate first, it destroys the entry object without
//deserializing it. Then "remove" cleans up the key.
try {
localRegion.invalidate(key);
localRegion.remove(key);
} catch (EntryNotFoundException enfe) { //If the entry has disappeared (or expired) by the time we try to remove it,
//then the GemFire API will throw an exception. But this is okay.
log.warning("Entry not found for key = " + key.toString(), enfe);
}
count++;
// Every 10000 is frequent enough to give you a quick pulse, but
// not so frequent as to spam your log.
if (count % 10000 == 0) {
log.info("Cleared " + count + "/" + regionSize + " entries for region " + regionName);
}
}
}
log.warning("Region cleared: " + regionName);
synchronized (clearingRegionList) {
clearingRegionList.remove(regionName);
}
} catch (RuntimeException rex) {
// Make sure we clean up our tracking list even in the unlikely event of a blowup.
clearingRegionList.remove(regionName);
log.error(rex.toString(), rex); // Log AND throw is bad, but from my experience, a RunTimeException
// CAN get sent all the way back to the client and never show
// up in gemfire.log. (If the exception happens before last result)
throw rex;
}
}
@Override
public String getId() {
return "clear-region-function";
}
@Override
public void init(Properties arg0) { }
@Override
public boolean hasResult() { return true; }
@Override
public boolean isHA() { return true; }
@Override
public boolean optimizeForWrite() {return true;}
}