/*
* 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;
import org.junit.experimental.categories.Category;
import org.junit.Test;
import static org.junit.Assert.*;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;
import java.io.File;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.VM;
/**
* Bug37377 DUNIT Test: The Clear operation during a GII in progress can leave a Entry in the Oplog
* due to a race condition wherein the clearFlag getting set after the entry gets written to the
* disk, The Test verifies the existence of the scenario.
*
*/
@Category(DistributedTest.class)
public class Bug37377DUnitTest extends JUnit4CacheTestCase {
protected static String regionName = "TestRegion";
static Properties props = new Properties();
protected static DistributedSystem distributedSystem = null;
VM vm0, vm1;
protected static Cache cache = null;
protected static File[] dirs = null;
private static final int maxEntries = 10000;
transient private static CountDownLatch clearLatch = new CountDownLatch(1);
static Boolean clearOccured = false;
public Bug37377DUnitTest() {
super();
File file1 = new File(getTestMethodName() + "1");
file1.mkdir();
file1.deleteOnExit();
File file2 = new File(getTestMethodName() + "2");
file2.mkdir();
file2.deleteOnExit();
dirs = new File[2];
dirs[0] = file1;
dirs[1] = file2;
}
@Override
public final void postSetUp() throws Exception {
final Host host = Host.getHost(0);
vm0 = host.getVM(0);
vm1 = host.getVM(1);
}
@Override
public final void preTearDownCacheTestCase() throws Exception {
vm1.invoke(() -> destroyRegion());
vm0.invoke(() -> destroyRegion());
}
/**
* This method is used to create Cache in VM0
*/
@SuppressWarnings("deprecation")
private void createCacheForVM0() {
try {
distributedSystem = (new Bug37377DUnitTest()).getSystem(props);
assertTrue(distributedSystem != null);
cache = CacheFactory.create(distributedSystem);
assertTrue(cache != null);
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
factory.setDiskSynchronous(false);
factory.setDiskStoreName(
cache.createDiskStoreFactory().setDiskDirs(dirs).create("Bug37377DUnitTest").getName());
RegionAttributes attr = factory.create();
cache.createRegion(regionName, attr);
} catch (Exception ex) {
ex.printStackTrace();
fail("Error Creating cache / region ");
}
}
/**
* This method is used to create Cache in VM1
*/
@SuppressWarnings("deprecation")
private void createCacheForVM1() {
try {
distributedSystem = (new Bug37377DUnitTest()).getSystem(props);
assertTrue(distributedSystem != null);
cache = CacheFactory.create(distributedSystem);
assertTrue("cache found null", cache != null);
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
factory.setDiskSynchronous(false);
factory.setDiskStoreName(
cache.createDiskStoreFactory().setDiskDirs(dirs).create("Bug37377DUnitTest").getName());
RegionAttributes attr = factory.create();
DistributedRegion distRegion = new DistributedRegion(regionName, attr, null,
(GemFireCacheImpl) cache, new InternalRegionArguments().setDestroyLockFlag(true)
.setRecreateFlag(false).setSnapshotInputStream(null).setImageTarget(null));
// assertTrue("Distributed Region is null", distRegion != null); (cannot be null)
TestAbstractDiskRegionEntry.setMembers(vm1, vm0); // vm1 is thisVM, vm0 is otherVM
((AbstractRegionMap) distRegion.entries)
.setEntryFactory(TestAbstractDiskRegionEntry.getEntryFactory());
LocalRegion region = (LocalRegion) ((GemFireCacheImpl) cache).createVMRegion(regionName, attr,
new InternalRegionArguments().setInternalMetaRegion(distRegion).setDestroyLockFlag(true)
.setSnapshotInputStream(null).setImageTarget(null));
assertTrue("Local Region is null", region != null);
} catch (Exception ex) {
ex.printStackTrace();
fail("Error Creating cache / region " + ex);
}
}
/**
* This method puts in maxEntries in the Region
*/
private void putSomeEntries() {
assertTrue("Cache is found as null ", cache != null);
Region rgn = cache.getRegion(regionName);
for (int i = 0; i < maxEntries; i++) {
rgn.put(new Long(i), new Long(i));
}
}
/**
* This method clears the region and notifies the other member when complete
*
* @throws InterruptedException
*/
private static void invokeRemoteClearAndWait(VM remoteVM, VM thisVM) {
remoteVM.invoke(() -> clearRegionAndNotify(thisVM));
try {
clearLatch.await();
} catch (InterruptedException e) {
fail("wait for remote clear to complete failed");
}
}
/**
* This method clears the region and notifies the other member when complete
*/
private static void clearRegionAndNotify(VM otherVM) {
assertTrue("Cache is found as null ", cache != null);
Region rgn = cache.getRegion(regionName);
rgn.clear();
otherVM.invoke(() -> notifyClearComplete());
}
/**
* Decrement countdown latch to notify clear complete
*/
private static void notifyClearComplete() {
clearLatch.countDown();
}
/**
* This method destroys the Region
*/
private void destroyRegion() {
try {
assertTrue("Cache is found as null ", cache != null);
Region rgn = cache.getRegion(regionName);
rgn.localDestroyRegion();
cache.close();
} catch (Exception ex) {
}
}
/**
* This method closes the cache on the specified VM
*/
private void closeCacheForVM(final int vmNo) {
if (vmNo == 0) {
cache.getRegion(regionName).localDestroyRegion();
}
assertTrue("Cache is found as null ", cache != null);
cache.close();
}
/**
* This method verifies that the reintialized region size is zero
*/
private void verifyExtraEntryFromOpLogs() {
assertTrue("Cache is found as null ", cache != null);
Region rgn = cache.getRegion(regionName);
// should be zero after clear
assertEquals(0, rgn.size());
}
/**
* The Clear operation during a GII in progress can leave a Entry in the Oplog due to a race
* condition wherein the clearFlag getting set after the entry gets written to the disk, The Test
* verifies the existence of the scenario.
*
*/
@Test
public void testGIIputWithClear() {
vm0.invoke(() -> createCacheForVM0());
vm0.invoke(() -> putSomeEntries());
vm1.invoke(() -> createCacheForVM1());
vm0.invoke(() -> closeCacheForVM(0));
vm1.invoke(() -> closeCacheForVM(1));
vm1.invoke(() -> createCacheForVM1());
vm1.invoke(() -> verifyExtraEntryFromOpLogs());
}
static class TestAbstractDiskRegionEntry extends VersionedThinDiskRegionEntryHeapObjectKey {
static private VM thisVM, otherVM;
static void setMembers(VM localVM, VM remoteVM) {
thisVM = localVM;
otherVM = remoteVM;
}
protected TestAbstractDiskRegionEntry(RegionEntryContext r, Object key, Object value) {
super(r, key, value);
}
private static RegionEntryFactory factory = new RegionEntryFactory() {
public final RegionEntry createEntry(RegionEntryContext r, Object key, Object value) {
return new TestAbstractDiskRegionEntry(r, key, value);
}
public final Class getEntryClass() {
return TestAbstractDiskRegionEntry.class;
}
public RegionEntryFactory makeVersioned() {
return this;
}
public RegionEntryFactory makeOnHeap() {
return this;
}
};
/**
* Overridden setValue method to call clear Region before actually writing the entry
*/
@Override
public boolean initialImageInit(final LocalRegion r, final long lastModifiedTime,
final Object newValue, final boolean create, final boolean wasRecovered,
final boolean versionTagAccepted) throws RegionClearedException {
synchronized (clearOccured) {
if (!clearOccured) {
// Force other member to perform a clear during our GII
invokeRemoteClearAndWait(otherVM, thisVM);
clearOccured = true;
}
}
// Continue GII processing, which should throw RegionClearedException after the clear
try {
boolean result = super.initialImageInit(r, lastModifiedTime, newValue, create, wasRecovered,
versionTagAccepted);
} catch (RegionClearedException rce) {
throw rce;
} catch (Exception ex) {
fail("Caught exception during initialImageInit: " + ex);
}
return true;
}
public static RegionEntryFactory getEntryFactory() {
return factory;
}
}
}