HBASE-12055 TestBucketWriterThread hangs flakily based on timing (Nick Dimiduk)

This commit is contained in:
Enis Soztutar 2014-09-22 14:41:12 -07:00
parent bcbacefdd5
commit 7b7648322b
2 changed files with 32 additions and 23 deletions

View File

@ -256,8 +256,9 @@ public class BucketCache implements BlockCache, HeapSize {
writerThreads[i] = new WriterThread(writerQueues.get(i), i); writerThreads[i] = new WriterThread(writerQueues.get(i), i);
writerThreads[i].setName(threadName + "-BucketCacheWriter-" + i); writerThreads[i].setName(threadName + "-BucketCacheWriter-" + i);
writerThreads[i].setDaemon(true); writerThreads[i].setDaemon(true);
writerThreads[i].start();
} }
startWriterThreads();
// Run the statistics thread periodically to print the cache statistics log // Run the statistics thread periodically to print the cache statistics log
// TODO: Add means of turning this off. Bit obnoxious running thread just to make a log // TODO: Add means of turning this off. Bit obnoxious running thread just to make a log
// every five minutes. // every five minutes.
@ -270,6 +271,17 @@ public class BucketCache implements BlockCache, HeapSize {
persistencePath + ", bucketAllocator=" + this.bucketAllocator); persistencePath + ", bucketAllocator=" + this.bucketAllocator);
} }
/**
* Called by the constructor to start the writer threads. Used by tests that need to override
* starting the threads.
*/
@VisibleForTesting
protected void startWriterThreads() {
for (WriterThread thread : writerThreads) {
thread.start();
}
}
@VisibleForTesting @VisibleForTesting
boolean isCacheEnabled() { boolean isCacheEnabled() {
return this.cacheEnabled; return this.cacheEnabled;

View File

@ -37,22 +37,35 @@ import java.util.List;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import static java.lang.Thread.State;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@Category({IOTests.class, SmallTests.class}) @Category({IOTests.class, SmallTests.class})
public class TestBucketWriterThread { public class TestBucketWriterThread {
public static final int MAX_NUMBER_OF_TRIES_BEFORE_TEST_FAILURE = 1000000;
private BucketCache bc; private BucketCache bc;
private BucketCache.WriterThread wt; private BucketCache.WriterThread wt;
private BlockingQueue<RAMQueueEntry> q; private BlockingQueue<RAMQueueEntry> q;
private Cacheable plainCacheable; private Cacheable plainCacheable;
private BlockCacheKey plainKey; private BlockCacheKey plainKey;
/** A BucketCache that does not start its writer threads. */
private static class MockBucketCache extends BucketCache {
public MockBucketCache(String ioEngineName, long capacity, int blockSize, int[] bucketSizes,
int writerThreadNum, int writerQLen, String persistencePath, int ioErrorsTolerationDuration)
throws FileNotFoundException, IOException {
super(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen,
persistencePath, ioErrorsTolerationDuration);
}
@Override
protected void startWriterThreads() {
// intentional noop
}
}
/** /**
* Set up variables and get BucketCache and WriterThread into state where tests can manually * Set up variables and get BucketCache and WriterThread into state where tests can manually
* control the running of WriterThread and BucketCache is empty. * control the running of WriterThread and BucketCache is empty.
@ -65,36 +78,20 @@ public class TestBucketWriterThread {
// Run with one writer thread only. Means there will be one writer queue only too. We depend // Run with one writer thread only. Means there will be one writer queue only too. We depend
// on this in below. // on this in below.
final int writerThreadsCount = 1; final int writerThreadsCount = 1;
this.bc = new BucketCache("heap", capacity, 1, new int [] {1}, writerThreadsCount, this.bc = new MockBucketCache("heap", capacity, 1, new int [] {1}, writerThreadsCount,
capacity, null, 100/*Tolerate ioerrors for 100ms*/); capacity, null, 100/*Tolerate ioerrors for 100ms*/);
assertEquals(writerThreadsCount, bc.writerThreads.length); assertEquals(writerThreadsCount, bc.writerThreads.length);
assertEquals(writerThreadsCount, bc.writerQueues.size()); assertEquals(writerThreadsCount, bc.writerQueues.size());
// Get reference to our single WriterThread instance. // Get reference to our single WriterThread instance.
this.wt = bc.writerThreads[0]; this.wt = bc.writerThreads[0];
this.q = bc.writerQueues.get(0); this.q = bc.writerQueues.get(0);
// On construction bucketcache WriterThread is blocked on the writer queue so it will not
// notice the disabling of the writer until after it has processed an entry. Lets pass one
// through after setting disable flag on the writer. We want to disable the WriterThread so
// we can run the doDrain manually so we can watch it working and assert it doing right thing.
for (int i = 0; i != MAX_NUMBER_OF_TRIES_BEFORE_TEST_FAILURE; i++) {
if (wt.getThread().getState() == State.RUNNABLE) {
Thread.sleep(1);
}
}
assertThat(wt.getThread().getState(), is(not(State.RUNNABLE)));
wt.disableWriter(); wt.disableWriter();
this.plainKey = new BlockCacheKey("f", 0); this.plainKey = new BlockCacheKey("f", 0);
this.plainCacheable = Mockito.mock(Cacheable.class); this.plainCacheable = Mockito.mock(Cacheable.class);
bc.cacheBlock(this.plainKey, plainCacheable);
for (int i = 0; i != MAX_NUMBER_OF_TRIES_BEFORE_TEST_FAILURE; i++) {
if (!bc.ramCache.isEmpty()) {
Thread.sleep(1);
}
}
assertThat(bc.ramCache.isEmpty(), is(true)); assertThat(bc.ramCache.isEmpty(), is(true));
assertTrue(q.isEmpty()); assertTrue(q.isEmpty());
// Now writer thread should be disabled.
} }
@After @After