| /* |
| * 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 static org.apache.geode.distributed.ConfigurationProperties.LOCATORS; |
| import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL; |
| import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.File; |
| import java.util.Arrays; |
| import java.util.Properties; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.FixMethodOrder; |
| import org.junit.Test; |
| import org.junit.runners.MethodSorters; |
| |
| import org.apache.geode.cache.AttributesFactory; |
| import org.apache.geode.cache.Cache; |
| import org.apache.geode.cache.CacheFactory; |
| import org.apache.geode.cache.CacheWriterException; |
| import org.apache.geode.cache.DataPolicy; |
| import org.apache.geode.cache.DiskStoreFactory; |
| import org.apache.geode.cache.EntryEvent; |
| import org.apache.geode.cache.EntryNotFoundException; |
| import org.apache.geode.cache.EvictionAction; |
| import org.apache.geode.cache.EvictionAttributes; |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.cache.RegionAttributes; |
| import org.apache.geode.cache.Scope; |
| import org.apache.geode.cache.TimeoutException; |
| import org.apache.geode.distributed.DistributedSystem; |
| import org.apache.geode.distributed.internal.DistributionManager; |
| import org.apache.geode.internal.InternalStatisticsDisabledException; |
| import org.apache.geode.internal.cache.DistributedRegion.DiskPosition; |
| import org.apache.geode.internal.cache.InitialImageOperation.Entry; |
| import org.apache.geode.internal.cache.eviction.EvictableEntry; |
| import org.apache.geode.internal.cache.eviction.EvictionController; |
| import org.apache.geode.internal.cache.eviction.EvictionCounters; |
| import org.apache.geode.internal.cache.eviction.EvictionList; |
| import org.apache.geode.internal.cache.eviction.EvictionNode; |
| import org.apache.geode.internal.cache.persistence.DiskRecoveryStore; |
| import org.apache.geode.internal.cache.versions.VersionSource; |
| import org.apache.geode.internal.cache.versions.VersionStamp; |
| import org.apache.geode.internal.cache.versions.VersionTag; |
| import org.apache.geode.internal.serialization.ByteArrayDataInput; |
| import org.apache.geode.internal.serialization.Version; |
| |
| /** |
| * This is a test verifies region is LIFO enabled by MEMORY verifies correct stats updating and |
| * faultin is not evicting another entry - not strict LIFO |
| * |
| * @since GemFire 5.7 |
| */ |
| @FixMethodOrder(MethodSorters.NAME_ASCENDING) |
| public class LIFOEvictionAlgoMemoryEnabledRegionJUnitTest { |
| |
| /** The cache instance */ |
| private static Cache cache = null; |
| |
| /** Stores LIFO Related Statistics */ |
| private static EvictionCounters lifoStats = null; |
| |
| /** The distributedSystem instance */ |
| private static DistributedSystem distributedSystem = null; |
| |
| private static String regionName = "LIFOMemoryEvictionEnabledRegion"; |
| |
| private static int maximumMegabytes = 1; |
| |
| private static int byteArraySize = 20480; |
| |
| private static long memEntryCountForFirstPutOperation; |
| |
| private int deltaSize = 20738; |
| |
| private static EvictionList lifoClockHand = null; |
| |
| |
| @Before |
| public void setUp() throws Exception { |
| initializeVM(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| assertNotNull(cache); |
| Region rgn = cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| rgn.localDestroyRegion(); |
| cache.close(); |
| |
| } |
| |
| /** |
| * Method for intializing the VM and create region with LIFO attached |
| */ |
| private static void initializeVM() throws Exception { |
| Properties props = new Properties(); |
| props.setProperty(MCAST_PORT, "0"); |
| props.setProperty(LOCATORS, ""); |
| props.setProperty(LOG_LEVEL, "info"); // to keep diskPerf logs smaller |
| distributedSystem = DistributedSystem.connect(props); |
| cache = CacheFactory.create(distributedSystem); |
| assertNotNull(cache); |
| DiskStoreFactory dsf = cache.createDiskStoreFactory(); |
| AttributesFactory factory = new AttributesFactory(); |
| factory.setScope(Scope.LOCAL); |
| |
| File dir = new File("testingDirectoryDefault"); |
| dir.mkdir(); |
| dir.deleteOnExit(); |
| File[] dirs = {dir}; |
| dsf.setDiskDirsAndSizes(dirs, new int[] {Integer.MAX_VALUE}); |
| dsf.setAutoCompact(false); |
| ((DiskStoreFactoryImpl) dsf).setDiskDirSizesUnit(DiskDirSizesUnit.BYTES); |
| |
| factory.setDiskStoreName(dsf.create(regionName).getName()); |
| factory.setDiskSynchronous(true); |
| factory.setDataPolicy(DataPolicy.NORMAL); |
| |
| /* setting LIFO MEMORY related eviction attributes */ |
| |
| factory.setEvictionAttributes(EvictionAttributes.createLIFOMemoryAttributes(maximumMegabytes, |
| EvictionAction.OVERFLOW_TO_DISK)); |
| RegionAttributes attr = factory.create(); |
| |
| ((GemFireCacheImpl) cache).createRegion(regionName, attr); |
| lifoClockHand = |
| ((VMLRURegionMap) ((LocalRegion) cache.getRegion(Region.SEPARATOR + regionName)).entries) |
| .getEvictionList(); |
| |
| /* storing stats reference */ |
| lifoStats = lifoClockHand.getStatistics(); |
| |
| } |
| |
| /** |
| * This test does the following :<br> |
| * 1)Perform put operation <br> |
| * 2)Verify count of faultin entries <br> |
| */ |
| @Test |
| public void test000EntryFaultinCount() { |
| try { |
| assertNotNull(cache); |
| LocalRegion rgn = (LocalRegion) cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| |
| DiskRegionStats diskRegionStats = rgn.getDiskRegion().getStats(); |
| assertTrue("Entry count not 0 ", new Long(0).equals(new Long(lifoStats.getCounter()))); |
| |
| // put 60 entries into the region |
| for (long i = 0L; i < 60L; i++) { |
| rgn.put("key" + i, newDummyObject(i)); |
| } |
| |
| assertEquals( |
| "LRU eviction entry count and entries overflown to disk count from diskstats is not equal ", |
| lifoStats.getEvictions(), diskRegionStats.getNumOverflowOnDisk()); |
| assertNull("Entry value in VM is not null", rgn.getValueInVM("key59")); |
| // used to get number of entries required to reach the limit of memory assign |
| memEntryCountForFirstPutOperation = diskRegionStats.getNumEntriesInVM(); |
| rgn.get("key59"); |
| assertEquals("Not equal to number of entries present in VM : ", 51L, |
| diskRegionStats.getNumEntriesInVM()); |
| assertEquals("Not equal to number of entries present on disk : ", 9L, |
| diskRegionStats.getNumOverflowOnDisk()); |
| |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail("Test failed"); |
| } |
| } |
| |
| /** |
| * This test does the following :<br> |
| * 1)Varify region is LIFO Enabled <br> |
| * 2)Perform put operation <br> |
| * 3)perform get operation <br> |
| * 4)Varify value retrived <br> |
| * 5)Verify count (entries present in memory) after put operations <br> |
| * 6)Verify count (entries present in memory) after get (performs faultin) operation <br> |
| * 7)Verify count (entries present in memory) after remove operation <br> |
| */ |
| @Test |
| public void test001LIFOStatsUpdation() { |
| try { |
| assertNotNull(cache); |
| LocalRegion rgn = (LocalRegion) cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| |
| // check for is LIFO Enable |
| assertTrue("Eviction Algorithm is not LIFO", |
| (((EvictionAttributesImpl) rgn.getAttributes().getEvictionAttributes()).isLIFO())); |
| |
| // put 60 entries into the region |
| for (long i = 0L; i < 60L; i++) { |
| rgn.put(new Long(i), newDummyObject(i)); |
| } |
| |
| // verifies evicted entry values are null in memory |
| assertTrue("In memory ", rgn.entries.getEntry(new Long(51)).isValueNull()); |
| assertTrue("In memory ", rgn.entries.getEntry(new Long(52)).isValueNull()); |
| assertTrue("In memory ", rgn.entries.getEntry(new Long(53)).isValueNull()); |
| |
| // get an entry back |
| rgn.get(new Long(46)); |
| rgn.get(new Long(51)); |
| rgn.get(new Long(56)); |
| // gets stuck in while loop |
| rgn.put(new Long(60), newDummyObject(60)); |
| rgn.put(new Long(61), newDummyObject(61)); |
| assertNull("Entry value in VM is not null", rgn.getValueInVM(new Long(58))); |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail("Test failed"); |
| } |
| |
| } |
| |
| /** |
| * This test does the following :<br> |
| * 1)Perform put operation <br> |
| * 2)Verify entry evicted is LIFO Entry and is not present in vm<br> |
| */ |
| @Test |
| public void test002LIFOEntryEviction() { |
| try { |
| assertNotNull(cache); |
| LocalRegion rgn = (LocalRegion) cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| |
| assertEquals("Region is not properly cleared ", 0, rgn.size()); |
| assertTrue("Entry count not 0 ", new Long(0).equals(new Long(lifoStats.getCounter()))); |
| // put sixty entries into the region |
| for (long i = 0L; i < 60L; i++) { |
| rgn.put(new Long(i), newDummyObject(i)); |
| if (i < memEntryCountForFirstPutOperation) { |
| // entries are in memory |
| assertNotNull("Entry is not in VM ", rgn.getValueInVM(new Long(i))); |
| } else { |
| /* |
| * assertTrue("LIFO Entry is not evicted", lifoClockHand.getLRUEntry() .testEvicted()); |
| */ |
| assertTrue("Entry is not null ", rgn.entries.getEntry(new Long(i)).isValueNull()); |
| } |
| } |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail("Test failed"); |
| } |
| } |
| |
| /** |
| * This test does the following :<br> |
| * 1)Perform put operation <br> |
| * 2)Verify count of evicted entries <br> |
| */ |
| @Test |
| public void test003EntryEvictionCount() { |
| try { |
| assertNotNull(cache); |
| Region rgn = cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| |
| assertTrue("Entry count not 0 ", new Long(0).equals(new Long(lifoStats.getCounter()))); |
| // put 60 entries into the region |
| for (long i = 0L; i < 60L; i++) { |
| rgn.put(new Long(i), newDummyObject(i)); |
| } |
| |
| assertTrue("1)Total eviction count is not correct ", |
| new Long(10).equals(new Long(lifoStats.getEvictions()))); |
| rgn.put(new Long(60), newDummyObject(60)); |
| rgn.get(new Long(55)); |
| assertTrue("2)Total eviction count is not correct ", |
| new Long(11).equals(new Long(lifoStats.getEvictions()))); |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail("Test failed"); |
| } |
| } |
| |
| // Basic checks to validate lifo queue implementation works as expected |
| @Test |
| public void testLIFOQueue() { |
| try { |
| assertNotNull(cache); |
| Region rgn = cache.getRegion(Region.SEPARATOR + regionName); |
| assertNotNull(rgn); |
| // insert data |
| lifoClockHand.appendEntry(new TestLRUNode(1)); |
| lifoClockHand.appendEntry(new TestLRUNode(2)); |
| lifoClockHand.appendEntry(new TestLRUNode(3)); |
| assertTrue(lifoClockHand.size() == 3); |
| // make sure data is removed in LIFO fashion |
| TestLRUNode tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("Value = " + tailValue.getValue(), tailValue.value == 3); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 2); |
| tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("Value = " + tailValue.getValue(), tailValue.value == 2); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 1); |
| tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("Value = " + tailValue.getValue(), tailValue.value == 1); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 0); |
| tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("No Value - null", tailValue == null); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 0); |
| // check that entries not available or already evicted are skipped and removed |
| TestLRUNode testlrunode = new TestLRUNode(1); |
| lifoClockHand.appendEntry(testlrunode); |
| testlrunode = new TestLRUNode(2); |
| testlrunode.setEvicted(); |
| lifoClockHand.appendEntry(testlrunode); |
| testlrunode = new TestLRUNode(3); |
| testlrunode.setEvicted(); |
| lifoClockHand.appendEntry(testlrunode); |
| tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("Value = " + tailValue.getValue(), tailValue.value == 1); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 0); |
| tailValue = (TestLRUNode) lifoClockHand.getEvictableEntry(); |
| assertTrue("No Value - null", tailValue == null); |
| assertTrue("LIFO Queue Size = " + lifoClockHand.size(), lifoClockHand.size() == 0); |
| // TODO : need tests for data still part of transaction |
| } catch (Exception ex) { |
| ex.printStackTrace(); |
| fail(ex.getMessage()); |
| } |
| } |
| |
| // purpose to create object ,size of byteArraySize |
| private Object newDummyObject(long i) { |
| byte[] value = new byte[byteArraySize]; |
| Arrays.fill(value, (byte) i); |
| return value; |
| } |
| |
| // test class for validating LIFO queue |
| static class TestLRUNode implements EvictableEntry { |
| |
| EvictionNode next = null; |
| EvictionNode prev = null; |
| boolean evicted = false; |
| boolean recentlyUsed = false; |
| int value = 0; |
| |
| public TestLRUNode(int value) { |
| this.value = value; |
| } |
| |
| @Override |
| public Token getValueAsToken() { |
| return null; |
| } |
| |
| @Override |
| public void setValueWithTombstoneCheck(final Object value, final EntryEvent event) |
| throws RegionClearedException { |
| |
| } |
| |
| @Override |
| public Object getTransformedValue() { |
| return null; |
| } |
| |
| @Override |
| public Object getValueInVM(final RegionEntryContext context) { |
| return null; |
| } |
| |
| @Override |
| public Object getValueOnDisk(final InternalRegion region) throws EntryNotFoundException { |
| return null; |
| } |
| |
| @Override |
| public Object getValueOnDiskOrBuffer(final InternalRegion region) |
| throws EntryNotFoundException { |
| return null; |
| } |
| |
| @Override |
| public boolean initialImagePut(final InternalRegion region, final long lastModified, |
| final Object newValue, final boolean wasRecovered, final boolean acceptedVersionTag) |
| throws RegionClearedException { |
| return false; |
| } |
| |
| @Override |
| public boolean initialImageInit(final InternalRegion region, final long lastModified, |
| final Object newValue, final boolean create, final boolean wasRecovered, |
| final boolean acceptedVersionTag) throws RegionClearedException { |
| return false; |
| } |
| |
| @Override |
| public boolean destroy(final InternalRegion region, final EntryEventImpl event, |
| final boolean inTokenMode, final boolean cacheWrite, final Object expectedOldValue, |
| final boolean forceDestroy, final boolean removeRecoveredEntry) throws CacheWriterException, |
| EntryNotFoundException, TimeoutException, RegionClearedException { |
| return false; |
| } |
| |
| @Override |
| public boolean getValueWasResultOfSearch() { |
| return false; |
| } |
| |
| @Override |
| public void setValueResultOfSearch(final boolean value) { |
| |
| } |
| |
| @Override |
| public Object getSerializedValueOnDisk(final InternalRegion region) { |
| return null; |
| } |
| |
| @Override |
| public Object getValueInVMOrDiskWithoutFaultIn(final InternalRegion region) { |
| return null; |
| } |
| |
| @Override |
| public Object getValueOffHeapOrDiskWithoutFaultIn(final InternalRegion region) { |
| return null; |
| } |
| |
| @Override |
| public boolean isUpdateInProgress() { |
| return false; |
| } |
| |
| @Override |
| public void setUpdateInProgress(final boolean underUpdate) { |
| |
| } |
| |
| @Override |
| public boolean isCacheListenerInvocationInProgress() { |
| return false; |
| } |
| |
| @Override |
| public void setCacheListenerInvocationInProgress(final boolean isListenerInvoked) { |
| |
| } |
| |
| @Override |
| public boolean isValueNull() { |
| return false; |
| } |
| |
| @Override |
| public boolean isInvalid() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDestroyed() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDestroyedOrRemoved() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDestroyedOrRemovedButNotTombstone() { |
| return false; |
| } |
| |
| @Override |
| public boolean isInvalidOrRemoved() { |
| return false; |
| } |
| |
| @Override |
| public void setValueToNull() { |
| |
| } |
| |
| @Override |
| public void returnToPool() { |
| |
| } |
| |
| @Override |
| public void setNext(EvictionNode next) { |
| this.next = next; |
| } |
| |
| @Override |
| public void setPrevious(EvictionNode previous) { |
| this.prev = previous; |
| } |
| |
| @Override |
| public EvictionNode next() { |
| return next; |
| } |
| |
| @Override |
| public EvictionNode previous() { |
| return prev; |
| } |
| |
| @Override |
| public int updateEntrySize(EvictionController ccHelper) { |
| return 0; |
| } |
| |
| @Override |
| public int updateEntrySize(EvictionController ccHelper, Object value) { |
| return 0; |
| } |
| |
| @Override |
| public int getEntrySize() { |
| return 0; |
| } |
| |
| @Override |
| public boolean isRecentlyUsed() { |
| return recentlyUsed; |
| } |
| |
| @Override |
| public void setRecentlyUsed(final RegionEntryContext context) { |
| recentlyUsed = true; |
| context.incRecentlyUsed(); |
| } |
| |
| @Override |
| public long getLastModified() { |
| return 0; |
| } |
| |
| @Override |
| public boolean hasStats() { |
| return false; |
| } |
| |
| @Override |
| public long getLastAccessed() throws InternalStatisticsDisabledException { |
| return 0; |
| } |
| |
| @Override |
| public long getHitCount() throws InternalStatisticsDisabledException { |
| return 0; |
| } |
| |
| @Override |
| public long getMissCount() throws InternalStatisticsDisabledException { |
| return 0; |
| } |
| |
| @Override |
| public void updateStatsForPut(final long lastModifiedTime, final long lastAccessedTime) { |
| |
| } |
| |
| @Override |
| public VersionStamp getVersionStamp() { |
| return null; |
| } |
| |
| @Override |
| public VersionTag generateVersionTag(final VersionSource member, final boolean withDelta, |
| final InternalRegion region, final EntryEventImpl event) { |
| return null; |
| } |
| |
| @Override |
| public boolean dispatchListenerEvents(final EntryEventImpl event) throws InterruptedException { |
| return false; |
| } |
| |
| @Override |
| public void updateStatsForGet(final boolean hit, final long time) { |
| |
| } |
| |
| @Override |
| public void txDidDestroy(final long currentTime) { |
| |
| } |
| |
| @Override |
| public void resetCounts() throws InternalStatisticsDisabledException { |
| |
| } |
| |
| @Override |
| public void makeTombstone(final InternalRegion region, final VersionTag version) |
| throws RegionClearedException { |
| |
| } |
| |
| @Override |
| public void removePhase1(final InternalRegion region, final boolean clear) |
| throws RegionClearedException { |
| |
| } |
| |
| @Override |
| public void removePhase2() { |
| |
| } |
| |
| @Override |
| public boolean isRemoved() { |
| return false; |
| } |
| |
| @Override |
| public boolean isRemovedPhase2() { |
| return false; |
| } |
| |
| @Override |
| public boolean isTombstone() { |
| return false; |
| } |
| |
| @Override |
| public boolean fillInValue(final InternalRegion region, final Entry entry, |
| final ByteArrayDataInput in, final DistributionManager distributionManager, |
| final Version version) { |
| return false; |
| } |
| |
| @Override |
| public boolean isOverflowedToDisk(final InternalRegion region, |
| final DiskPosition diskPosition) { |
| return false; |
| } |
| |
| @Override |
| public Object getKey() { |
| return null; |
| } |
| |
| @Override |
| public Object getValue(final RegionEntryContext context) { |
| return null; |
| } |
| |
| @Override |
| public Object getValueRetain(final RegionEntryContext context) { |
| return null; |
| } |
| |
| @Override |
| public void setValue(final RegionEntryContext context, final Object value) |
| throws RegionClearedException { |
| |
| } |
| |
| @Override |
| public void setValue(final RegionEntryContext context, final Object value, |
| final EntryEventImpl event) throws RegionClearedException { |
| |
| } |
| |
| @Override |
| public Object getValueRetain(final RegionEntryContext context, final boolean decompress) { |
| return null; |
| } |
| |
| @Override |
| public Object getValue() { |
| return null; |
| } |
| |
| @Override |
| public void unsetRecentlyUsed() { |
| recentlyUsed = false; |
| } |
| |
| @Override |
| public void setEvicted() { |
| evicted = true; |
| } |
| |
| @Override |
| public void unsetEvicted() { |
| evicted = false; |
| } |
| |
| @Override |
| public boolean isEvicted() { |
| return evicted; |
| } |
| |
| @Override |
| public boolean isInUseByTransaction() { |
| return false; |
| } |
| |
| @Override |
| public void incRefCount() { |
| |
| } |
| |
| @Override |
| public void decRefCount(final EvictionList lruList, final InternalRegion region) { |
| |
| } |
| |
| @Override |
| public void resetRefCount(final EvictionList lruList) { |
| |
| } |
| |
| @Override |
| public Object prepareValueForCache(final RegionEntryContext context, final Object value, |
| final boolean isEntryUpdate) { |
| return null; |
| } |
| |
| @Override |
| public Object prepareValueForCache(final RegionEntryContext context, final Object value, |
| final EntryEventImpl event, final boolean isEntryUpdate) { |
| return null; |
| } |
| |
| @Override |
| public Object getKeyForSizing() { |
| return null; |
| } |
| |
| @Override |
| public void setDelayedDiskId(final DiskRecoveryStore diskRecoveryStore) { |
| |
| } |
| } |
| } |