/* * 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(); } }