View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  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,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.executor;
20  
21  import static org.junit.Assert.*;
22  import java.io.IOException;
23  import java.io.StringWriter;
24  import java.util.concurrent.ThreadPoolExecutor;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.*;
32  import org.apache.hadoop.hbase.Waiter.Predicate;
33  import org.apache.hadoop.hbase.executor.ExecutorService.Executor;
34  import org.apache.hadoop.hbase.executor.ExecutorService.ExecutorStatus;
35  import org.apache.hadoop.hbase.testclassification.SmallTests;
36  import org.junit.Test;
37  import org.junit.experimental.categories.Category;
38  
39  import static org.mockito.Mockito.*;
40  
41  @Category(SmallTests.class)
42  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
43    justification="Use of an atomic type both as monitor and condition variable is intended")
44  public class TestExecutorService {
45    private static final Log LOG = LogFactory.getLog(TestExecutorService.class);
46  
47    @Test
48    public void testExecutorService() throws Exception {
49      int maxThreads = 5;
50      int maxTries = 10;
51      int sleepInterval = 10;
52  
53      Server mockedServer = mock(Server.class);
54      when(mockedServer.getConfiguration()).thenReturn(HBaseConfiguration.create());
55  
56      // Start an executor service pool with max 5 threads
57      ExecutorService executorService = new ExecutorService("unit_test");
58      executorService.startExecutorService(
59        ExecutorType.MASTER_SERVER_OPERATIONS, maxThreads);
60  
61      Executor executor =
62        executorService.getExecutor(ExecutorType.MASTER_SERVER_OPERATIONS);
63      ThreadPoolExecutor pool = executor.threadPoolExecutor;
64  
65      // Assert no threads yet
66      assertEquals(0, pool.getPoolSize());
67  
68      AtomicBoolean lock = new AtomicBoolean(true);
69      AtomicInteger counter = new AtomicInteger(0);
70  
71      // Submit maxThreads executors.
72      for (int i = 0; i < maxThreads; i++) {
73        executorService.submit(
74          new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN,
75              lock, counter));
76      }
77  
78      // The TestEventHandler will increment counter when it starts.
79      int tries = 0;
80      while (counter.get() < maxThreads && tries < maxTries) {
81        LOG.info("Waiting for all event handlers to start...");
82        Thread.sleep(sleepInterval);
83        tries++;
84      }
85  
86      // Assert that pool is at max threads.
87      assertEquals(maxThreads, counter.get());
88      assertEquals(maxThreads, pool.getPoolSize());
89  
90      ExecutorStatus status = executor.getStatus();
91      assertTrue(status.queuedEvents.isEmpty());
92      assertEquals(5, status.running.size());
93      checkStatusDump(status);
94  
95  
96      // Now interrupt the running Executor
97      synchronized (lock) {
98        lock.set(false);
99        lock.notifyAll();
100     }
101 
102     // Executor increments counter again on way out so.... test that happened.
103     while (counter.get() < (maxThreads * 2) && tries < maxTries) {
104       System.out.println("Waiting for all event handlers to finish...");
105       Thread.sleep(sleepInterval);
106       tries++;
107     }
108 
109     assertEquals(maxThreads * 2, counter.get());
110     assertEquals(maxThreads, pool.getPoolSize());
111 
112     // Add more than the number of threads items.
113     // Make sure we don't get RejectedExecutionException.
114     for (int i = 0; i < (2 * maxThreads); i++) {
115       executorService.submit(
116         new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN,
117             lock, counter));
118     }
119     // Now interrupt the running Executor
120     synchronized (lock) {
121       lock.set(false);
122       lock.notifyAll();
123     }
124 
125     // Make sure threads are still around even after their timetolive expires.
126     Thread.sleep(ExecutorService.Executor.keepAliveTimeInMillis * 2);
127     assertEquals(maxThreads, pool.getPoolSize());
128 
129     executorService.shutdown();
130 
131     assertEquals(0, executorService.getAllExecutorStatuses().size());
132 
133     // Test that submit doesn't throw NPEs
134     executorService.submit(
135       new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN,
136             lock, counter));
137   }
138 
139   private void checkStatusDump(ExecutorStatus status) throws IOException {
140     StringWriter sw = new StringWriter();
141     status.dumpTo(sw, "");
142     String dump = sw.toString();
143     LOG.info("Got status dump:\n" + dump);
144 
145     assertTrue(dump.contains("Waiting on java.util.concurrent.atomic.AtomicBoolean"));
146   }
147 
148   public static class TestEventHandler extends EventHandler {
149     private AtomicBoolean lock;
150     private AtomicInteger counter;
151 
152     public TestEventHandler(Server server, EventType eventType,
153                             AtomicBoolean lock, AtomicInteger counter) {
154       super(server, eventType);
155       this.lock = lock;
156       this.counter = counter;
157     }
158 
159     @Override
160     public void process() throws IOException {
161       int num = counter.incrementAndGet();
162       LOG.info("Running process #" + num + ", threadName=" +
163         Thread.currentThread().getName());
164       synchronized (lock) {
165         while (lock.get()) {
166           try {
167             lock.wait();
168           } catch (InterruptedException e) {
169             // do nothing
170           }
171         }
172       }
173       counter.incrementAndGet();
174     }
175   }
176 
177   @Test
178   public void testAborting() throws Exception {
179     final Configuration conf = HBaseConfiguration.create();
180     final Server server = mock(Server.class);
181     when(server.getConfiguration()).thenReturn(conf);
182 
183     ExecutorService executorService = new ExecutorService("unit_test");
184     executorService.startExecutorService(
185       ExecutorType.MASTER_SERVER_OPERATIONS, 1);
186 
187 
188     executorService.submit(new EventHandler(server, EventType.M_SERVER_SHUTDOWN) {
189       @Override
190       public void process() throws IOException {
191         throw new RuntimeException("Should cause abort");
192       }
193     });
194 
195     Waiter.waitFor(conf, 30000, new Predicate<Exception>() {
196       @Override
197       public boolean evaluate() throws Exception {
198         try {
199           verify(server, times(1)).abort(anyString(), (Throwable) anyObject());
200           return true;
201         } catch (Throwable t) {
202           return false;
203         }
204       }
205     });
206 
207     executorService.shutdown();
208   }
209 
210 }
211