/* * 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.junit.Assert.*; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.junit.experimental.categories.Category; import com.jayway.awaitility.Awaitility; import org.apache.geode.cache.Scope; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.test.junit.categories.IntegrationTest; /** * The test will verify <br> * 1. Multiple oplogs are being rolled at once <br> * 2. The Number of entries getting logged to the HTree are taking care of creation */ @Category(IntegrationTest.class) public class MultipleOplogsRollingFeatureJUnitTest extends DiskRegionTestingBase { private volatile boolean FLAG = false; private Object mutex = new Object(); private boolean CALLBACK_SET = false; private DiskRegionProperties diskProps = new DiskRegionProperties(); @Override protected final void preTearDown() throws Exception { LocalRegion.ISSUE_CALLBACKS_TO_CACHE_OBSERVER = false; } @Override protected final void postTearDown() throws Exception { System.clearProperty(DistributionConfig.GEMFIRE_PREFIX + "MAX_OPLOGS_PER_COMPACTION"); diskProps.setDiskDirs(dirs); } /** * The test will verify <br> * 1. Multiple oplogs are being rolled at once 2. The Number of entries are properly conflated */ @Test public void testMultipleRolling() throws Exception { System.setProperty(DistributionConfig.GEMFIRE_PREFIX + "MAX_OPLOGS_PER_COMPACTION", "17"); deleteFiles(); diskProps.setMaxOplogSize(450); diskProps.setCompactionThreshold(100); region = DiskRegionHelperFactory.getSyncPersistOnlyRegion(cache, diskProps, Scope.LOCAL); assertNotNull(region); DiskRegion diskRegion = ((LocalRegion) region).getDiskRegion(); assertNotNull(diskRegion); LocalRegion.ISSUE_CALLBACKS_TO_CACHE_OBSERVER = true; CacheObserverHolder.setInstance(getCacheObserver()); try { CALLBACK_SET = true; assertEquals(null, diskRegion.getOplogToBeCompacted()); logWriter.info("testMultipleRolling adding entry 1"); addEntries(1 /* oplogNumber */, 50 /* byte array size */); ((LocalRegion) region).getDiskStore().forceCompaction(); Awaitility.waitAtMost(15, TimeUnit.SECONDS).until(() -> FLAG == true); logWriter.info("testMultipleRolling after waitForCompactor"); // the compactor copied two tombstone and 1 entry to oplog #2 // The total oplog size will become 429, that why we need to // set oplogmaxsize to be 450. After compaction, the size become 151 // the compactor thread is now stuck waiting for mutex.notify // check the oplog rolling is null (since the compactor was able to delete it) if (diskRegion.getOplogToBeCompacted() != null) { logWriter.info("testMultipleRolling OplogToBeCompacted=" + java.util.Arrays.toString(diskRegion.getOplogToBeCompacted())); } assertEquals(null, diskRegion.getOplogToBeCompacted()); // update key3 with 360 bytes to cause it not fit in oplog #2 so it will create oplog #3 // this does an update to key 3 (3 is no longer in oplog#2) // So oplog#2 is EMPTY. logWriter.info("testMultipleRolling adding entry 2"); addEntries(2 /* oplogNumber */, 360 /* byte array size */); // #2 is now owned by the compactor so even though it is empty it // will not be deleted until the compactor does so. // only one oplog to be compacted because there's no drf assertEquals(1, diskRegion.getOplogToBeCompacted().length); // This add will not fit in oplog #3 so it will create oplog #4 // It does an update to key 3 (so 3 is no longer in oplog#3) // which empties it out logWriter.info("testMultipleRolling adding entry 3"); addEntries(3 /* oplogNumber */, 180 /* byte array size */); // #3 is was ready to be compacted but since it was EMPTIED // but because the compaction thread (the pool defaults to 1 thread) // is hung it will not be deleted immedaitely. assertEquals(2, diskRegion.getOplogToBeCompacted().length); // this add will fit in the current oplog // Just added an update to oplog #4 logWriter.info("testMultipleRolling adding entry 4"); addEntries(4 /* oplogNumber */, 1 /* byte array size */); assertEquals(2, diskRegion.getOplogToBeCompacted().length); logWriter.info("testMultipleRolling forceRolling"); region.forceRolling(); assertEquals(3, diskRegion.getOplogToBeCompacted().length); } finally { synchronized (mutex) { // let the compactor go CALLBACK_SET = false; FLAG = false; logWriter.info("testMultipleRolling letting compactor go"); mutex.notify(); } } // let the main thread sleep so that rolling gets over Awaitility.waitAtMost(25, TimeUnit.SECONDS).until(() -> FLAG == true); assertTrue("Number of Oplogs to be rolled is not null : this is unexpected", diskRegion.getOplogToBeCompacted() == null); cache.close(); cache = createCache(); region = DiskRegionHelperFactory.getSyncPersistOnlyRegion(cache, diskProps, Scope.LOCAL); assertTrue("Recreated region size is not 1 ", region.size() == 1); closeDown(); deleteFiles(); } private void addEntries(int opLogNum, int valueSize) { assertNotNull(region); byte[] val = new byte[valueSize]; for (int i = 0; i < valueSize; ++i) { val[i] = (byte) i; } // Creating opLog1 if (opLogNum == 1) { for (int i = 1; i < 4; i++) { // create 3 entries region.create(new Integer(i), val); } // destroy Entry 1 and 2 region.destroy(new Integer(1)); region.destroy(new Integer(2)); } else if (opLogNum == 2) { // update Entry 3 region.put(new Integer(3), val); } else if (opLogNum == 3) { // update Entry 3 region.put(new Integer(3), val); } else if (opLogNum == 4) { // update Entry 3 region.put(new Integer(3), val); } } private CacheObserver getCacheObserver() { return (new CacheObserverAdapter() { public void beforeGoingToCompact() { if (logWriter.fineEnabled()) { logWriter.fine("In beforeGoingToCompact"); } } public void afterHavingCompacted() { FLAG = true; if (CALLBACK_SET) { synchronized (mutex) { try { mutex.wait(); } catch (InterruptedException e) { fail("interrupted"); } } } if (logWriter.fineEnabled()) { logWriter.fine("In afterHavingCompacted"); } } }); } }