View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   * http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.io.hfile.bucket;
20  
21  import org.apache.hadoop.hbase.HBaseConfiguration;
22  import org.apache.hadoop.hbase.testclassification.SmallTests;
23  import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
24  import org.apache.hadoop.hbase.io.hfile.Cacheable;
25  import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.BucketEntry;
26  import org.apache.hadoop.hbase.io.hfile.bucket.BucketCache.RAMQueueEntry;
27  import org.junit.After;
28  import org.junit.Before;
29  import org.junit.Test;
30  import org.junit.experimental.categories.Category;
31  import org.mockito.Mockito;
32  
33  import java.io.FileNotFoundException;
34  import java.io.IOException;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.concurrent.BlockingQueue;
38  import java.util.concurrent.atomic.AtomicLong;
39  
40  import static org.hamcrest.CoreMatchers.is;
41  import static org.junit.Assert.assertEquals;
42  import static org.junit.Assert.assertThat;
43  import static org.junit.Assert.assertTrue;
44  
45  @Category(SmallTests.class)
46  public class TestBucketWriterThread {
47    private BucketCache bc;
48    private BucketCache.WriterThread wt;
49    private BlockingQueue<RAMQueueEntry> q;
50    private Cacheable plainCacheable;
51    private BlockCacheKey plainKey;
52  
53    /** A BucketCache that does not start its writer threads. */
54    private static class MockBucketCache extends BucketCache {
55  
56      public MockBucketCache(String ioEngineName, long capacity, int blockSize, int[] bucketSizes,
57        int writerThreadNum, int writerQLen, String persistencePath, int ioErrorsTolerationDuration)
58        throws FileNotFoundException, IOException {
59        super(ioEngineName, capacity, blockSize, bucketSizes, writerThreadNum, writerQLen,
60          persistencePath, ioErrorsTolerationDuration, HBaseConfiguration.create());
61      }
62  
63      @Override
64      protected void startWriterThreads() {
65        // intentional noop
66      }
67    }
68  
69    /**
70     * Set up variables and get BucketCache and WriterThread into state where tests can  manually
71     * control the running of WriterThread and BucketCache is empty.
72     * @throws Exception
73     */
74    @Before
75    public void setUp() throws Exception {
76      // Arbitrary capacity.
77      final int capacity = 16;
78      // Run with one writer thread only. Means there will be one writer queue only too.  We depend
79      // on this in below.
80      final int writerThreadsCount = 1;
81      this.bc = new MockBucketCache("heap", capacity, 1, new int [] {1}, writerThreadsCount,
82        capacity, null, 100/*Tolerate ioerrors for 100ms*/);
83      assertEquals(writerThreadsCount, bc.writerThreads.length);
84      assertEquals(writerThreadsCount, bc.writerQueues.size());
85      // Get reference to our single WriterThread instance.
86      this.wt = bc.writerThreads[0];
87      this.q = bc.writerQueues.get(0);
88  
89      wt.disableWriter();
90      this.plainKey = new BlockCacheKey("f", 0);
91      this.plainCacheable = Mockito.mock(Cacheable.class);
92  
93      assertThat(bc.ramCache.isEmpty(), is(true));
94      assertTrue(q.isEmpty());
95    }
96  
97    @After
98    public void tearDown() throws Exception {
99      if (this.bc != null) this.bc.shutdown();
100   }
101 
102   /**
103    * Test non-error case just works.
104    * @throws FileNotFoundException
105    * @throws IOException
106    * @throws InterruptedException
107    */
108   @Test (timeout=30000)
109   public void testNonErrorCase() throws IOException, InterruptedException {
110     bc.cacheBlock(this.plainKey, this.plainCacheable);
111     doDrainOfOneEntry(this.bc, this.wt, this.q);
112   }
113 
114   /**
115    * Pass through a too big entry and ensure it is cleared from queues and ramCache.
116    * Manually run the WriterThread.
117    * @throws InterruptedException
118    */
119   @Test
120   public void testTooBigEntry() throws InterruptedException {
121     Cacheable tooBigCacheable = Mockito.mock(Cacheable.class);
122     Mockito.when(tooBigCacheable.getSerializedLength()).thenReturn(Integer.MAX_VALUE);
123     this.bc.cacheBlock(this.plainKey, tooBigCacheable);
124     doDrainOfOneEntry(this.bc, this.wt, this.q);
125   }
126 
127   /**
128    * Do IOE. Take the RAMQueueEntry that was on the queue, doctor it to throw exception, then
129    * put it back and process it.
130    * @throws IOException
131    * @throws InterruptedException
132    */
133   @SuppressWarnings("unchecked")
134   @Test (timeout=30000)
135   public void testIOE() throws IOException, InterruptedException {
136     this.bc.cacheBlock(this.plainKey, plainCacheable);
137     RAMQueueEntry rqe = q.remove();
138     RAMQueueEntry spiedRqe = Mockito.spy(rqe);
139     Mockito.doThrow(new IOException("Mocked!")).when(spiedRqe).
140       writeToCache((IOEngine)Mockito.any(), (BucketAllocator)Mockito.any(),
141         (UniqueIndexMap<Integer>)Mockito.any(), (AtomicLong)Mockito.any());
142     this.q.add(spiedRqe);
143     doDrainOfOneEntry(bc, wt, q);
144     // Cache disabled when ioes w/o ever healing.
145     assertTrue(!bc.isCacheEnabled());
146   }
147 
148   /**
149    * Do Cache full exception
150    * @throws IOException
151    * @throws InterruptedException
152    */
153   @Test (timeout=30000)
154   public void testCacheFullException()
155       throws IOException, InterruptedException {
156     this.bc.cacheBlock(this.plainKey, plainCacheable);
157     RAMQueueEntry rqe = q.remove();
158     RAMQueueEntry spiedRqe = Mockito.spy(rqe);
159     final CacheFullException cfe = new CacheFullException(0, 0);
160     BucketEntry mockedBucketEntry = Mockito.mock(BucketEntry.class);
161     Mockito.doThrow(cfe).
162       doReturn(mockedBucketEntry).
163       when(spiedRqe).writeToCache((IOEngine)Mockito.any(), (BucketAllocator)Mockito.any(),
164         (UniqueIndexMap<Integer>)Mockito.any(), (AtomicLong)Mockito.any());
165     this.q.add(spiedRqe);
166     doDrainOfOneEntry(bc, wt, q);
167   }
168 
169   private static void doDrainOfOneEntry(final BucketCache bc, final BucketCache.WriterThread wt,
170       final BlockingQueue<RAMQueueEntry> q)
171   throws InterruptedException {
172     List<RAMQueueEntry> rqes = BucketCache.getRAMQueueEntries(q, new ArrayList<RAMQueueEntry>(1));
173     wt.doDrain(rqes);
174     assertTrue(q.isEmpty());
175     assertTrue(bc.ramCache.isEmpty());
176     assertEquals(0, bc.heapSize());
177   }
178 }