Return-Path: X-Original-To: apmail-hbase-commits-archive@www.apache.org Delivered-To: apmail-hbase-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 2999B726B for ; Thu, 6 Oct 2011 21:40:10 +0000 (UTC) Received: (qmail 38970 invoked by uid 500); 6 Oct 2011 21:40:10 -0000 Delivered-To: apmail-hbase-commits-archive@hbase.apache.org Received: (qmail 38945 invoked by uid 500); 6 Oct 2011 21:40:10 -0000 Mailing-List: contact commits-help@hbase.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@hbase.apache.org Delivered-To: mailing list commits@hbase.apache.org Received: (qmail 38938 invoked by uid 99); 6 Oct 2011 21:40:10 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 06 Oct 2011 21:40:10 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 06 Oct 2011 21:40:08 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 0F00E23888FD for ; Thu, 6 Oct 2011 21:39:48 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1179868 - in /hbase/trunk: ./ src/main/java/org/apache/hadoop/hbase/io/hfile/slab/ src/main/java/org/apache/hadoop/hbase/regionserver/ src/test/java/org/apache/hadoop/hbase/io/hfile/ src/test/java/org/apache/hadoop/hbase/io/hfile/slab/ Date: Thu, 06 Oct 2011 21:39:47 -0000 To: commits@hbase.apache.org From: stack@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20111006213948.0F00E23888FD@eris.apache.org> Author: stack Date: Thu Oct 6 21:39:47 2011 New Revision: 1179868 URL: http://svn.apache.org/viewvc?rev=1179868&view=rev Log: HBASE-4482 Race Condition Concerning Eviction in SlabCache Modified: hbase/trunk/CHANGES.txt hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SingleSizeCache.java hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabCache.java hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabItemEvictionWatcher.java hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/CacheTestUtils.java hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSingleSizeCache.java hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSlabCache.java Modified: hbase/trunk/CHANGES.txt URL: http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/CHANGES.txt (original) +++ hbase/trunk/CHANGES.txt Thu Oct 6 21:39:47 2011 @@ -342,6 +342,7 @@ Release 0.92.0 - Unreleased HBASE-4481 TestMergeTool failed in 0.92 build 20 HBASE-4386 Fix a potential NPE in TaskMonitor (todd) HBASE-4402 Retaining locality after restart broken + HBASE-4482 Race Condition Concerning Eviction in SlabCache (Li Pi) TESTS HBASE-4450 test for number of blocks read: to serve as baseline for expected Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SingleSizeCache.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SingleSizeCache.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SingleSizeCache.java (original) +++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SingleSizeCache.java Thu Oct 6 21:39:47 2011 @@ -58,8 +58,8 @@ public class SingleSizeCache implements private final int blockSize; private final CacheStats stats; private final SlabItemEvictionWatcher evictionWatcher; - private AtomicLong size; - private AtomicLong timeSinceLastAccess; + private final AtomicLong size; + private final AtomicLong timeSinceLastAccess; public final static long CACHE_FIXED_OVERHEAD = ClassSize .align((2 * Bytes.SIZEOF_INT) + (5 * ClassSize.REFERENCE) + +ClassSize.OBJECT); @@ -87,13 +87,15 @@ public class SingleSizeCache implements this.size = new AtomicLong(CACHE_FIXED_OVERHEAD + backingStore.heapSize()); this.timeSinceLastAccess = new AtomicLong(); - // This evictionListener is called whenever the cache automatically evicts + // This evictionListener is called whenever the cache automatically + // evicts // something. MapEvictionListener listener = new MapEvictionListener() { @Override public void onEviction(String key, CacheablePair value) { timeSinceLastAccess.set(System.nanoTime() - value.recentlyAccessed.get()); + stats.evict(); doEviction(key, value); } }; @@ -107,12 +109,6 @@ public class SingleSizeCache implements public void cacheBlock(String blockName, Cacheable toBeCached) { ByteBuffer storedBlock; - /* - * Spinlock if empty, Guava Mapmaker guarantees that we will not store more - * items than the memory we have allocated, but the Slab Allocator may still - * be empty if we have not yet completed eviction - */ - try { storedBlock = backingStore.alloc(toBeCached.getSerializedLength()); } catch (InterruptedException e) { @@ -171,6 +167,7 @@ public class SingleSizeCache implements public boolean evictBlock(String key) { stats.evict(); CacheablePair evictedBlock = backingMap.remove(key); + if (evictedBlock != null) { doEviction(key, evictedBlock); } @@ -200,8 +197,9 @@ public class SingleSizeCache implements // Thread A sees the null serializedData, and returns null // Thread A calls cacheBlock on the same block, and gets // "already cached" since the block is still in backingStore + if (evictionWatcher != null) { - evictionWatcher.onEviction(key, false); + evictionWatcher.onEviction(key, this); } } stats.evicted(); @@ -210,7 +208,7 @@ public class SingleSizeCache implements public void logStats() { - long milliseconds = (long) this.timeSinceLastAccess.get() / 1000000; + long milliseconds = this.timeSinceLastAccess.get() / 1000000; LOG.info("For Slab of size " + this.blockSize + ": " + this.getOccupiedSize() / this.blockSize Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabCache.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabCache.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabCache.java (original) +++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabCache.java Thu Oct 6 21:39:47 2011 @@ -21,8 +21,8 @@ package org.apache.hadoop.hbase.io.hfile.slab; import java.math.BigDecimal; -import java.util.Map.Entry; import java.util.List; +import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; @@ -122,7 +122,9 @@ public class SlabCache implements SlabIt + sizes.length + " slabs " + "offheapslabporportions and offheapslabsizes"); } - /* We use BigDecimals instead of floats because float rounding is annoying */ + /* + * We use BigDecimals instead of floats because float rounding is annoying + */ BigDecimal[] parsedProportions = stringArrayToBigDecimalArray(porportions); BigDecimal[] parsedSizes = stringArrayToBigDecimalArray(sizes); @@ -205,12 +207,37 @@ public class SlabCache implements SlabIt this.successfullyCachedStats.addin(cachedItem.getSerializedLength()); SingleSizeCache scache = scacheEntry.getValue(); - /*This will throw a runtime exception if we try to cache the same value twice*/ + /* + * This will throw a runtime exception if we try to cache the same value + * twice + */ scache.cacheBlock(blockName, cachedItem); - /*Spinlock, if we're spinlocking, that means an eviction hasn't taken place yet*/ - while (backingStore.putIfAbsent(blockName, scache) != null) { - Thread.yield(); + /* + * If an eviction for this value hasn't taken place yet, we want to wait for + * it to take place. See HBase-4330. + */ + SingleSizeCache replace; + while ((replace = backingStore.putIfAbsent(blockName, scache)) != null) { + synchronized (replace) { + /* + * With the exception of unit tests, this should happen extremely + * rarely. + */ + try { + replace.wait(); + } catch (InterruptedException e) { + LOG.warn("InterruptedException on the caching thread: " + e); + } + } + } + + /* + * Let the eviction threads know that something has been cached, and let + * them try their hand at eviction + */ + synchronized (scache) { + scache.notifyAll(); } } @@ -254,25 +281,70 @@ public class SlabCache implements SlabIt * the evict counter. */ public boolean evictBlock(String key) { - stats.evict(); - return onEviction(key, true); - } - - @Override - public boolean onEviction(String key, boolean callAssignedCache) { - SingleSizeCache cacheEntry = backingStore.remove(key); + SingleSizeCache cacheEntry = backingStore.get(key); if (cacheEntry == null) { return false; + } else { + cacheEntry.evictBlock(key); + return true; } - /* we need to bump up stats.evict, as this call came from the assignedCache. */ - if (callAssignedCache == false) { - stats.evict(); + } + + @Override + public void onEviction(String key, Object notifier) { + /* + * Without the while loop below, the following can occur: + * + * Invariant: Anything in SingleSizeCache will have a representation in + * SlabCache, and vice-versa. + * + * Start: Key A is in both SingleSizeCache and SlabCache. Invariant is + * satisfied + * + * Thread A: Caches something, starting eviction of Key A in SingleSizeCache + * + * Thread B: Checks for Key A -> Returns Gets Null, as eviction has begun + * + * Thread B: Recaches Key A, gets to SingleSizeCache, does not get the + * PutIfAbsentLoop yet... + * + * Thread C: Caches another key, starting the second eviction of Key A. + * + * Thread A: does its onEviction, removing the entry of Key A from + * SlabCache. + * + * Thread C: does its onEviction, removing the (blank) entry of Key A from + * SlabCache: + * + * Thread B: goes to putifabsent, and puts its entry into SlabCache. + * + * Result: SlabCache has an entry for A, while SingleSizeCache has no + * entries for A. Invariant is violated. + * + * What the while loop does, is that, at the end, it GUARANTEES that an + * onEviction will remove an entry. See HBase-4482. + */ + + stats.evict(); + while ((backingStore.remove(key)) == null) { + /* With the exception of unit tests, this should happen extremely rarely. */ + synchronized (notifier) { + try { + notifier.wait(); + } catch (InterruptedException e) { + LOG.warn("InterruptedException on the evicting thread: " + e); + } + } } stats.evicted(); - if (callAssignedCache) { - cacheEntry.evictBlock(key); + + /* + * Now we've evicted something, lets tell the caching threads to try to + * cache something. + */ + synchronized (notifier) { + notifier.notifyAll(); } - return true; } /** @@ -346,7 +418,8 @@ public class SlabCache implements SlabIt * */ static class SlabStats { - // the maximum size somebody will ever try to cache, then we multiply by 10 + // the maximum size somebody will ever try to cache, then we multiply by + // 10 // so we have finer grained stats. final int MULTIPLIER = 10; final int NUMDIVISIONS = (int) (Math.log(Integer.MAX_VALUE) * MULTIPLIER); @@ -368,11 +441,11 @@ public class SlabCache implements SlabIt } double getUpperBound(int index) { - return Math.pow(Math.E, ((double) (index + 0.5) / (double) MULTIPLIER)); + return Math.pow(Math.E, ((index + 0.5) / MULTIPLIER)); } double getLowerBound(int index) { - return Math.pow(Math.E, ((double) (index - 0.5) / (double) MULTIPLIER)); + return Math.pow(Math.E, ((index - 0.5) / MULTIPLIER)); } public void logStats() { Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabItemEvictionWatcher.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabItemEvictionWatcher.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabItemEvictionWatcher.java (original) +++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/io/hfile/slab/SlabItemEvictionWatcher.java Thu Oct 6 21:39:47 2011 @@ -30,9 +30,10 @@ interface SlabItemEvictionWatcher { * SingleSizeSlabCaches. * * @param key the key of the item being evicted + * @param notifier the object notifying the SlabCache of the eviction. * @param boolean callAssignedCache whether we should call the cache which the * key was originally assigned to. */ - boolean onEviction(String key, boolean callAssignedCache); + void onEviction(String key, Object notifier); } Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java (original) +++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java Thu Oct 6 21:39:47 2011 @@ -386,17 +386,14 @@ public class StoreFile { MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); long cacheSize = (long)(mu.getMax() * cachePercentage); int blockSize = conf.getInt("hbase.offheapcache.minblocksize", HFile.DEFAULT_BLOCKSIZE); + long offHeapCacheSize = (long) (conf.getFloat("hbase.offheapcache.percentage", (float) 0.95) * DirectMemoryUtils.getDirectMemorySize()); boolean enableOffHeapCache = conf.getBoolean("hbase.offheapcache.enable", false); - long offHeapCacheSize = enableOffHeapCache ? - (long) (conf.getFloat("hbase.offheapcache.percentage", - (float) 0.95) * DirectMemoryUtils.getDirectMemorySize()) : - 0; LOG.info("Allocating LruBlockCache with maximum size " + StringUtils.humanReadableInt(cacheSize)); - if(offHeapCacheSize <= 0) { + if(offHeapCacheSize <= 0 || !enableOffHeapCache) { hfileBlockCache = new LruBlockCache(cacheSize, DEFAULT_BLOCKSIZE_SMALL); } else { - LOG.info("Allocating OffHeapCache with maximum size " + + LOG.info("Allocating OffHeapCache with maximum size " + StringUtils.humanReadableInt(offHeapCacheSize)); hfileBlockCache = new DoubleBlockCache(cacheSize, offHeapCacheSize, DEFAULT_BLOCKSIZE_SMALL, blockSize, conf); } Modified: hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/CacheTestUtils.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/CacheTestUtils.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/CacheTestUtils.java (original) +++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/CacheTestUtils.java Thu Oct 6 21:39:47 2011 @@ -19,8 +19,11 @@ */ package org.apache.hadoop.hbase.io.hfile; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; import java.nio.ByteBuffer; @@ -102,7 +105,7 @@ public class CacheTestUtils { Thread.sleep(10); } ctx.stop(); - if ((double) hits.get() / ((double) hits.get() + (double) miss.get()) < passingScore) { + if (hits.get() / ((double) hits.get() + (double) miss.get()) < passingScore) { fail("Too many nulls returned. Hits: " + hits.get() + " Misses: " + miss.get()); } @@ -201,7 +204,7 @@ public class CacheTestUtils { TestThread t = new MultithreadedTestUtil.RepeatingTestThread(ctx) { @Override public void doAnAction() throws Exception { - for (int j = 0; j < 10; j++) { + for (int j = 0; j < 100; j++) { String key = "key_" + finalI + "_" + j; Arrays.fill(buf, (byte) (finalI * j)); final ByteArrayCacheable bac = new ByteArrayCacheable(buf); Modified: hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSingleSizeCache.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSingleSizeCache.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSingleSizeCache.java (original) +++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSingleSizeCache.java Thu Oct 6 21:39:47 2011 @@ -20,8 +20,9 @@ package org.apache.hadoop.hbase.io.hfile.slab; import org.apache.hadoop.hbase.io.hfile.CacheTestUtils; -import org.apache.hadoop.hbase.io.hfile.slab.SingleSizeCache; -import org.junit.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; /** * Tests SingleSlabCache. @@ -48,28 +49,28 @@ public class TestSingleSizeCache { cache.shutdown(); } - @Ignore @Test + @Test public void testCacheSimple() throws Exception { CacheTestUtils.testCacheSimple(cache, BLOCK_SIZE, NUM_QUERIES); } - @Ignore @Test + @Test public void testCacheMultiThreaded() throws Exception { CacheTestUtils.testCacheMultiThreaded(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES, 0.80); } - @Ignore @Test + @Test public void testCacheMultiThreadedSingleKey() throws Exception { CacheTestUtils.hammerSingleKey(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES); } - @Ignore @Test + @Test public void testCacheMultiThreadedEviction() throws Exception { CacheTestUtils.hammerEviction(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES); } - @Ignore @Test + @Test public void testHeapSizeChanges(){ CacheTestUtils.testHeapSizeChanges(cache, BLOCK_SIZE); } Modified: hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSlabCache.java URL: http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSlabCache.java?rev=1179868&r1=1179867&r2=1179868&view=diff ============================================================================== --- hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSlabCache.java (original) +++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/io/hfile/slab/TestSlabCache.java Thu Oct 6 21:39:47 2011 @@ -19,16 +19,15 @@ */ package org.apache.hadoop.hbase.io.hfile.slab; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.io.hfile.CacheTestUtils; -import org.apache.hadoop.hbase.io.hfile.slab.SlabCache; import org.apache.hadoop.hbase.io.hfile.slab.SlabCache.SlabStats; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.Ignore; - -import static org.junit.Assert.*; /** * Basic test of SlabCache. Puts and gets. @@ -59,36 +58,36 @@ public class TestSlabCache { cache.shutdown(); } - @Ignore @Test + @Test public void testElementPlacement() { - assertEquals(cache.getHigherBlock((int) BLOCK_SIZE).getKey().intValue(), - (int) (BLOCK_SIZE * 11 / 10)); - assertEquals(cache.getHigherBlock((int) (BLOCK_SIZE * 2)).getKey() - .intValue(), (int) (BLOCK_SIZE * 21 / 10)); + assertEquals(cache.getHigherBlock(BLOCK_SIZE).getKey().intValue(), + (BLOCK_SIZE * 11 / 10)); + assertEquals(cache.getHigherBlock((BLOCK_SIZE * 2)).getKey() + .intValue(), (BLOCK_SIZE * 21 / 10)); } - @Ignore @Test + @Test public void testCacheSimple() throws Exception { CacheTestUtils.testCacheSimple(cache, BLOCK_SIZE, NUM_QUERIES); } - @Ignore @Test + @Test public void testCacheMultiThreaded() throws Exception { CacheTestUtils.testCacheMultiThreaded(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES, 0.80); } - @Ignore @Test + @Test public void testCacheMultiThreadedSingleKey() throws Exception { CacheTestUtils.hammerSingleKey(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES); } - @Ignore @Test + @Test public void testCacheMultiThreadedEviction() throws Exception { CacheTestUtils.hammerEviction(cache, BLOCK_SIZE, 10, NUM_QUERIES); } - @Ignore @Test + @Test /*Just checks if ranges overlap*/ public void testStatsArithmetic(){ SlabStats test = cache.requestStats; @@ -99,7 +98,7 @@ public class TestSlabCache { } } - @Ignore @Test + @Test public void testHeapSizeChanges(){ CacheTestUtils.testHeapSizeChanges(cache, BLOCK_SIZE); }