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  import junit.framework.TestSuite;
31  
32  import org.apache.hadoop.hbase.util.PoolMap.PoolType;
33  
34  public class TestPoolMap {
35    public abstract static class TestPoolType extends TestCase {
36      protected PoolMap<String, String> poolMap;
37      protected Random random = new Random();
38  
39      protected static final int POOL_SIZE = 3;
40  
41      @Override
42      protected void setUp() throws Exception {
43        this.poolMap = new PoolMap<String, String>(getPoolType(), POOL_SIZE);
44      }
45  
46      protected abstract PoolType getPoolType();
47  
48      @Override
49      protected void tearDown() throws Exception {
50        this.poolMap.clear();
51      }
52  
53      protected void runThread(final String randomKey, final String randomValue,
54          final String expectedValue) throws InterruptedException {
55        final AtomicBoolean matchFound = new AtomicBoolean(false);
56        Thread thread = new Thread(new Runnable() {
57          @Override
58          public void run() {
59            poolMap.put(randomKey, randomValue);
60            String actualValue = poolMap.get(randomKey);
61            matchFound.set(expectedValue == null ? actualValue == null
62                : expectedValue.equals(actualValue));
63          }
64        });
65        thread.start();
66        thread.join();
67        assertTrue(matchFound.get());
68      }
69    }
70  
71    public static class TestRoundRobinPoolType extends TestPoolType {
72      @Override
73      protected PoolType getPoolType() {
74        return PoolType.RoundRobin;
75      }
76  
77      public void testSingleThreadedClient() throws InterruptedException,
78          ExecutionException {
79        String randomKey = String.valueOf(random.nextInt());
80        String randomValue = String.valueOf(random.nextInt());
81        // As long as the pool is not full, we'll get null back.
82        // This forces the user to create new values that can be used to populate
83        // the pool.
84        runThread(randomKey, randomValue, null);
85        assertEquals(1, poolMap.size(randomKey));
86      }
87  
88      public void testMultiThreadedClients() throws InterruptedException,
89          ExecutionException {
90        for (int i = 0; i < POOL_SIZE; i++) {
91          String randomKey = String.valueOf(random.nextInt());
92          String randomValue = String.valueOf(random.nextInt());
93          // As long as the pool is not full, we'll get null back
94          runThread(randomKey, randomValue, null);
95          // As long as we use distinct keys, each pool will have one value
96          assertEquals(1, poolMap.size(randomKey));
97        }
98        poolMap.clear();
99        String randomKey = String.valueOf(random.nextInt());
100       for (int i = 0; i < POOL_SIZE - 1; i++) {
101         String randomValue = String.valueOf(random.nextInt());
102         // As long as the pool is not full, we'll get null back
103         runThread(randomKey, randomValue, null);
104         // since we use the same key, the pool size should grow
105         assertEquals(i + 1, poolMap.size(randomKey));
106       }
107       // at the end of the day, there should be as many values as we put
108       assertEquals(POOL_SIZE - 1, poolMap.size(randomKey));
109     }
110 
111     public void testPoolCap() throws InterruptedException, ExecutionException {
112       String randomKey = String.valueOf(random.nextInt());
113       List<String> randomValues = new ArrayList<String>();
114       for (int i = 0; i < POOL_SIZE * 2; i++) {
115         String randomValue = String.valueOf(random.nextInt());
116         randomValues.add(randomValue);
117         if (i < POOL_SIZE - 1) {
118           // As long as the pool is not full, we'll get null back
119           runThread(randomKey, randomValue, null);
120         } else {
121           // when the pool becomes full, we expect the value we get back to be
122           // what we put earlier, in round-robin order
123           runThread(randomKey, randomValue,
124               randomValues.get((i - POOL_SIZE + 1) % POOL_SIZE));
125         }
126       }
127       assertEquals(POOL_SIZE, poolMap.size(randomKey));
128     }
129 
130   }
131 
132   public static class TestThreadLocalPoolType extends TestPoolType {
133     @Override
134     protected PoolType getPoolType() {
135       return PoolType.ThreadLocal;
136     }
137 
138     public void testSingleThreadedClient() throws InterruptedException,
139         ExecutionException {
140       String randomKey = String.valueOf(random.nextInt());
141       String randomValue = String.valueOf(random.nextInt());
142       // As long as the pool is not full, we should get back what we put
143       runThread(randomKey, randomValue, randomValue);
144       assertEquals(1, poolMap.size(randomKey));
145     }
146 
147     public void testMultiThreadedClients() throws InterruptedException,
148         ExecutionException {
149       // As long as the pool is not full, we should get back what we put
150       for (int i = 0; i < POOL_SIZE; i++) {
151         String randomKey = String.valueOf(random.nextInt());
152         String randomValue = String.valueOf(random.nextInt());
153         runThread(randomKey, randomValue, randomValue);
154         assertEquals(1, poolMap.size(randomKey));
155       }
156       String randomKey = String.valueOf(random.nextInt());
157       for (int i = 0; i < POOL_SIZE; i++) {
158         String randomValue = String.valueOf(random.nextInt());
159         runThread(randomKey, randomValue, randomValue);
160         assertEquals(i + 1, poolMap.size(randomKey));
161       }
162     }
163 
164     public void testPoolCap() throws InterruptedException, ExecutionException {
165       String randomKey = String.valueOf(random.nextInt());
166       for (int i = 0; i < POOL_SIZE * 2; i++) {
167         String randomValue = String.valueOf(random.nextInt());
168         // as of HBASE-4150, pool limit is no longer used with ThreadLocalPool
169           runThread(randomKey, randomValue, randomValue);
170       }
171       assertEquals(POOL_SIZE * 2, poolMap.size(randomKey));
172     }
173 
174   }
175 
176   public static class TestReusablePoolType extends TestPoolType {
177     @Override
178     protected PoolType getPoolType() {
179       return PoolType.Reusable;
180     }
181 
182     public void testSingleThreadedClient() throws InterruptedException,
183         ExecutionException {
184       String randomKey = String.valueOf(random.nextInt());
185       String randomValue = String.valueOf(random.nextInt());
186       // As long as we poll values we put, the pool size should remain zero
187       runThread(randomKey, randomValue, randomValue);
188       assertEquals(0, poolMap.size(randomKey));
189     }
190 
191     public void testMultiThreadedClients() throws InterruptedException,
192         ExecutionException {
193       // As long as we poll values we put, the pool size should remain zero
194       for (int i = 0; i < POOL_SIZE; i++) {
195         String randomKey = String.valueOf(random.nextInt());
196         String randomValue = String.valueOf(random.nextInt());
197         runThread(randomKey, randomValue, randomValue);
198         assertEquals(0, poolMap.size(randomKey));
199       }
200       poolMap.clear();
201       String randomKey = String.valueOf(random.nextInt());
202       for (int i = 0; i < POOL_SIZE - 1; i++) {
203         String randomValue = String.valueOf(random.nextInt());
204         runThread(randomKey, randomValue, randomValue);
205         assertEquals(0, poolMap.size(randomKey));
206       }
207       assertEquals(0, poolMap.size(randomKey));
208     }
209 
210     public void testPoolCap() throws InterruptedException, ExecutionException {
211       // As long as we poll values we put, the pool size should remain zero
212       String randomKey = String.valueOf(random.nextInt());
213       List<String> randomValues = new ArrayList<String>();
214       for (int i = 0; i < POOL_SIZE * 2; i++) {
215         String randomValue = String.valueOf(random.nextInt());
216         randomValues.add(randomValue);
217         runThread(randomKey, randomValue, randomValue);
218       }
219       assertEquals(0, poolMap.size(randomKey));
220     }
221 
222   }
223 
224   public static Test suite() {
225     TestSuite suite = new TestSuite();
226     suite.addTestSuite(TestRoundRobinPoolType.class);
227     suite.addTestSuite(TestThreadLocalPoolType.class);
228     suite.addTestSuite(TestReusablePoolType.class);
229     return suite;
230   }
231 
232 }