1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.util;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Random;
25  import java.util.concurrent.ExecutionException;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import junit.framework.Test;
29  import junit.framework.TestCase;
30  
31  import org.apache.hadoop.hbase.MediumTests;
32  import org.apache.hadoop.hbase.SmallTests;
33  import org.apache.hadoop.hbase.util.PoolMap.PoolType;
34  import org.junit.experimental.categories.Category;
35  import org.junit.runner.RunWith;
36  import org.junit.runners.Suite;
37  
38  @RunWith(Suite.class)
39  @Suite.SuiteClasses({TestPoolMap.TestRoundRobinPoolType.class, TestPoolMap.TestThreadLocalPoolType.class, TestPoolMap.TestReusablePoolType.class})
40  @Category(MediumTests.class)
41  public class TestPoolMap {
42    public abstract static class TestPoolType extends TestCase {
43      protected PoolMap<String, String> poolMap;
44      protected Random random = new Random();
45  
46      protected static final int POOL_SIZE = 3;
47  
48      @Override
49      protected void setUp() throws Exception {
50        this.poolMap = new PoolMap<String, String>(getPoolType(), POOL_SIZE);
51      }
52  
53      protected abstract PoolType getPoolType();
54  
55      @Override
56      protected void tearDown() throws Exception {
57        this.poolMap.clear();
58      }
59  
60      protected void runThread(final String randomKey, final String randomValue,
61          final String expectedValue) throws InterruptedException {
62        final AtomicBoolean matchFound = new AtomicBoolean(false);
63        Thread thread = new Thread(new Runnable() {
64          @Override
65          public void run() {
66            poolMap.put(randomKey, randomValue);
67            String actualValue = poolMap.get(randomKey);
68            matchFound.set(expectedValue == null ? actualValue == null
69                : expectedValue.equals(actualValue));
70          }
71        });
72        thread.start();
73        thread.join();
74        assertTrue(matchFound.get());
75      }
76    }
77  
78    @Category(MediumTests.class)
79    public static class TestRoundRobinPoolType extends TestPoolType {
80      @Override
81      protected PoolType getPoolType() {
82        return PoolType.RoundRobin;
83      }
84  
85      public void testSingleThreadedClient() throws InterruptedException,
86          ExecutionException {
87        String randomKey = String.valueOf(random.nextInt());
88        String randomValue = String.valueOf(random.nextInt());
89        // As long as the pool is not full, we'll get null back.
90        // This forces the user to create new values that can be used to populate
91        // the pool.
92        runThread(randomKey, randomValue, null);
93        assertEquals(1, poolMap.size(randomKey));
94      }
95  
96      public void testMultiThreadedClients() throws InterruptedException,
97          ExecutionException {
98        for (int i = 0; i < POOL_SIZE; i++) {
99          String randomKey = String.valueOf(random.nextInt());
100         String randomValue = String.valueOf(random.nextInt());
101         // As long as the pool is not full, we'll get null back
102         runThread(randomKey, randomValue, null);
103         // As long as we use distinct keys, each pool will have one value
104         assertEquals(1, poolMap.size(randomKey));
105       }
106       poolMap.clear();
107       String randomKey = String.valueOf(random.nextInt());
108       for (int i = 0; i < POOL_SIZE - 1; i++) {
109         String randomValue = String.valueOf(random.nextInt());
110         // As long as the pool is not full, we'll get null back
111         runThread(randomKey, randomValue, null);
112         // since we use the same key, the pool size should grow
113         assertEquals(i + 1, poolMap.size(randomKey));
114       }
115       // at the end of the day, there should be as many values as we put
116       assertEquals(POOL_SIZE - 1, poolMap.size(randomKey));
117     }
118 
119     public void testPoolCap() throws InterruptedException, ExecutionException {
120       String randomKey = String.valueOf(random.nextInt());
121       List<String> randomValues = new ArrayList<String>();
122       for (int i = 0; i < POOL_SIZE * 2; i++) {
123         String randomValue = String.valueOf(random.nextInt());
124         randomValues.add(randomValue);
125         if (i < POOL_SIZE - 1) {
126           // As long as the pool is not full, we'll get null back
127           runThread(randomKey, randomValue, null);
128         } else {
129           // when the pool becomes full, we expect the value we get back to be
130           // what we put earlier, in round-robin order
131           runThread(randomKey, randomValue,
132               randomValues.get((i - POOL_SIZE + 1) % POOL_SIZE));
133         }
134       }
135       assertEquals(POOL_SIZE, poolMap.size(randomKey));
136     }
137 
138   }
139 
140   @Category(MediumTests.class)
141   public static class TestThreadLocalPoolType extends TestPoolType {
142     @Override
143     protected PoolType getPoolType() {
144       return PoolType.ThreadLocal;
145     }
146 
147     public void testSingleThreadedClient() throws InterruptedException,
148         ExecutionException {
149       String randomKey = String.valueOf(random.nextInt());
150       String randomValue = String.valueOf(random.nextInt());
151       // As long as the pool is not full, we should get back what we put
152       runThread(randomKey, randomValue, randomValue);
153       assertEquals(1, poolMap.size(randomKey));
154     }
155 
156     public void testMultiThreadedClients() throws InterruptedException,
157         ExecutionException {
158       // As long as the pool is not full, we should get back what we put
159       for (int i = 0; i < POOL_SIZE; i++) {
160         String randomKey = String.valueOf(random.nextInt());
161         String randomValue = String.valueOf(random.nextInt());
162         runThread(randomKey, randomValue, randomValue);
163         assertEquals(1, poolMap.size(randomKey));
164       }
165       String randomKey = String.valueOf(random.nextInt());
166       for (int i = 0; i < POOL_SIZE; i++) {
167         String randomValue = String.valueOf(random.nextInt());
168         runThread(randomKey, randomValue, randomValue);
169         assertEquals(i + 1, poolMap.size(randomKey));
170       }
171     }
172 
173     public void testPoolCap() throws InterruptedException, ExecutionException {
174       String randomKey = String.valueOf(random.nextInt());
175       for (int i = 0; i < POOL_SIZE * 2; i++) {
176         String randomValue = String.valueOf(random.nextInt());
177         // as of HBASE-4150, pool limit is no longer used with ThreadLocalPool
178           runThread(randomKey, randomValue, randomValue);
179       }
180       assertEquals(POOL_SIZE * 2, poolMap.size(randomKey));
181     }
182 
183   }
184 
185   @Category(MediumTests.class)
186   public static class TestReusablePoolType extends TestPoolType {
187     @Override
188     protected PoolType getPoolType() {
189       return PoolType.Reusable;
190     }
191 
192     public void testSingleThreadedClient() throws InterruptedException,
193         ExecutionException {
194       String randomKey = String.valueOf(random.nextInt());
195       String randomValue = String.valueOf(random.nextInt());
196       // As long as we poll values we put, the pool size should remain zero
197       runThread(randomKey, randomValue, randomValue);
198       assertEquals(0, poolMap.size(randomKey));
199     }
200 
201     public void testMultiThreadedClients() throws InterruptedException,
202         ExecutionException {
203       // As long as we poll values we put, the pool size should remain zero
204       for (int i = 0; i < POOL_SIZE; i++) {
205         String randomKey = String.valueOf(random.nextInt());
206         String randomValue = String.valueOf(random.nextInt());
207         runThread(randomKey, randomValue, randomValue);
208         assertEquals(0, poolMap.size(randomKey));
209       }
210       poolMap.clear();
211       String randomKey = String.valueOf(random.nextInt());
212       for (int i = 0; i < POOL_SIZE - 1; i++) {
213         String randomValue = String.valueOf(random.nextInt());
214         runThread(randomKey, randomValue, randomValue);
215         assertEquals(0, poolMap.size(randomKey));
216       }
217       assertEquals(0, poolMap.size(randomKey));
218     }
219 
220     public void testPoolCap() throws InterruptedException, ExecutionException {
221       // As long as we poll values we put, the pool size should remain zero
222       String randomKey = String.valueOf(random.nextInt());
223       List<String> randomValues = new ArrayList<String>();
224       for (int i = 0; i < POOL_SIZE * 2; i++) {
225         String randomValue = String.valueOf(random.nextInt());
226         randomValues.add(randomValue);
227         runThread(randomKey, randomValue, randomValue);
228       }
229       assertEquals(0, poolMap.size(randomKey));
230     }
231 
232   }
233 
234   @org.junit.Rule
235   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
236     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
237 }
238