/*
* 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.fail;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.Scope;
import org.apache.geode.test.junit.categories.IntegrationTest;
import static org.junit.Assert.*;
/**
* This is a bugtest for bug 37500.
*
*
*/
@Category(IntegrationTest.class)
public class Bug37500JUnitTest extends DiskRegionTestingBase {
/** The disk region configuration object for the test */
private DiskRegionProperties diskProps = new DiskRegionProperties();
/** The key for entry1 */
static final String KEY1 = "KEY1";
/** The key for entry2 */
static final String KEY2 = "KEY2";
/** Boolean to indicate the roller thread to proceed */
static volatile boolean proceedForRolling = false;
/**
* Boolean to decide whether we want to allow roller to run ( used via CacheObserver callback
*/
static volatile boolean notifyRoller = false;
/**
* This test does the following: <br>
* 1. Create a disk-region with following configurations :
* <li>dirSize = 2000 bytes
* <li>maxOplogSize = 500 bytes
* <li>rolling = true
* <li>syncMode = true
* <li>approx size on disk for operations = 440 bytes<br>
*
* 2.Make Roller go into WAIT state via CacheObserverAdapter.beforeGoingToCompact callback<br>
* 3.Put 440 bytes , it will go in oplog1 <br>
* 4.Put another 440 bytes ,it will go in oplog1<br>
* 5.Put 440 bytes , switching will be caused, it will go in oplog2, Roller will remained blocked
* (step 2)<br>
* 6.Put 440 bytes , it will go in oplog2, oplog2 will now be full<br>
* 7.Notify the Roller and put 440 bytes , this will try further switching. The put will fail with
* exception due to bug 37500. The put thread takes an entry level lock for entry2 ( the one with
* KEY2) and tries to write to disk but there is no free space left, so it goes into wait,
* expecting Roller to free up the space. The roller, which has now been notified to run, tries to
* roll entry2 for which it seeks entry level lock which has been acquired by put-thread. So the
* put thread eventually comes out of the wait with DiskAccessException<br>
*
* Another scenario for this bug is, once the disk space was getting exhausted , the entry
* operation threads which had already taken a lock on Entry got stuck trying to seek the Oplog
* Lock. The switching thread had acquired the Oplog.lock & was waiting for the roller thread to
* free disk space. Since the roller needed to acquire Entry lock to roll, it was unable to do so
* because of entry operation threads. This would cause the entry operation threads to get
* DiskAccessException after completing the stipulated wait. The Roller was able to free space
* only when it has rolled all the relevant entries which could happen only when the entry
* operation threads released the entry lock after getting DiskAccessException.
*
*
* @throws Exception
*/
@Test
public void testBug37500() throws Exception {
final int MAX_OPLOG_SIZE = 1000;
diskProps.setMaxOplogSize(MAX_OPLOG_SIZE);
diskProps.setPersistBackup(true);
diskProps.setRolling(true);
diskProps.setSynchronous(false);
File testdir = new File("bug37500-diskDir");
testdir.mkdir();
testdir.deleteOnExit();
diskProps.setDiskDirsAndSizes(new File[] {testdir}, new int[] {2000});
LocalRegion.ISSUE_CALLBACKS_TO_CACHE_OBSERVER = true;
region = DiskRegionHelperFactory.getSyncPersistOnlyRegion(cache, diskProps, Scope.LOCAL);
CacheObserver old = CacheObserverHolder.setInstance(new CacheObserverAdapter() {
public void beforeGoingToCompact() {
if (!proceedForRolling) {
synchronized (Bug37500JUnitTest.class) {
if (!proceedForRolling) {
try {
cache.getLogger().info("beforeGoingToCompact :: going into wait");
Bug37500JUnitTest.class.wait();
} catch (InterruptedException e) {
cache.getLogger().info("Roller interrupted");
fail("interrupted");
}
cache.getLogger().info("beforeGoingToCompact :: coming out of wait");
}
}
}
}
public void beforeSwitchingOplog() {
if (notifyRoller) {
cache.getLogger().info("beforeSwitchingOplog :: going to notify Roller");
synchronized (Bug37500JUnitTest.class) {
proceedForRolling = true;
Bug37500JUnitTest.class.notify();
cache.getLogger().info("beforeSwitchingOplog :: notified the Roller");
}
}
}
});
cache.getLogger().info("goin to put no. 1");
// put 440 bytes , it will go in oplog1
region.put(KEY1, new byte[420]);
cache.getLogger().info("goin to put no. 2");
// put another 440 bytes ,it will go in oplog1
region.put(KEY2, new byte[420]);
cache.getLogger().info("goin to put no. 3");
// put 440 bytes , switching will be caused, it will go in oplog2 (value
// size increased to 432 as key wont be written to disk for UPDATE)
region.put(KEY1, new byte[432]);
cache.getLogger().info("goin to put no. 4");
// put 440 bytes , it will go in oplog2
region.put(KEY1, new byte[432]);
notifyRoller = true;
cache.getLogger().info("goin to put no. 5");
// put 440 bytes , this will try further switching
region.put(KEY2, new byte[432]);
LocalRegion.ISSUE_CALLBACKS_TO_CACHE_OBSERVER = false;
CacheObserverHolder.setInstance(old);
closeDown();
}
}