001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.executor; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022import static org.mockito.ArgumentMatchers.any; 023import static org.mockito.ArgumentMatchers.anyString; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.times; 026import static org.mockito.Mockito.verify; 027import static org.mockito.Mockito.when; 028 029import java.io.IOException; 030import java.io.StringWriter; 031import java.util.concurrent.CountDownLatch; 032import java.util.concurrent.ThreadPoolExecutor; 033import java.util.concurrent.TimeUnit; 034import java.util.concurrent.atomic.AtomicBoolean; 035import java.util.concurrent.atomic.AtomicInteger; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.HBaseConfiguration; 038import org.apache.hadoop.hbase.Server; 039import org.apache.hadoop.hbase.Waiter; 040import org.apache.hadoop.hbase.Waiter.Predicate; 041import org.apache.hadoop.hbase.executor.ExecutorService.Executor; 042import org.apache.hadoop.hbase.executor.ExecutorService.ExecutorConfig; 043import org.apache.hadoop.hbase.executor.ExecutorService.ExecutorStatus; 044import org.apache.hadoop.hbase.testclassification.MiscTests; 045import org.apache.hadoop.hbase.testclassification.SmallTests; 046import org.junit.jupiter.api.Tag; 047import org.junit.jupiter.api.Test; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051@Tag(MiscTests.TAG) 052@Tag(SmallTests.TAG) 053public class TestExecutorService { 054 055 private static final Logger LOG = LoggerFactory.getLogger(TestExecutorService.class); 056 057 @Test 058 public void testExecutorService() throws Exception { 059 int maxThreads = 5; 060 int maxTries = 10; 061 int sleepInterval = 10; 062 063 Server mockedServer = mock(Server.class); 064 when(mockedServer.getConfiguration()).thenReturn(HBaseConfiguration.create()); 065 066 // Start an executor service pool with max 5 threads 067 ExecutorService executorService = new ExecutorService("unit_test"); 068 executorService.startExecutorService(executorService.new ExecutorConfig() 069 .setExecutorType(ExecutorType.MASTER_SERVER_OPERATIONS).setCorePoolSize(maxThreads)); 070 071 Executor executor = executorService.getExecutor(ExecutorType.MASTER_SERVER_OPERATIONS); 072 ThreadPoolExecutor pool = executor.threadPoolExecutor; 073 074 // Assert no threads yet 075 assertEquals(0, pool.getPoolSize()); 076 077 AtomicBoolean lock = new AtomicBoolean(true); 078 AtomicInteger counter = new AtomicInteger(0); 079 080 // Submit maxThreads executors. 081 for (int i = 0; i < maxThreads; i++) { 082 executorService 083 .submit(new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN, lock, counter)); 084 } 085 086 // The TestEventHandler will increment counter when it starts. 087 int tries = 0; 088 while (counter.get() < maxThreads && tries < maxTries) { 089 LOG.info("Waiting for all event handlers to start..."); 090 Thread.sleep(sleepInterval); 091 tries++; 092 } 093 094 // Assert that pool is at max threads. 095 assertEquals(maxThreads, counter.get()); 096 assertEquals(maxThreads, pool.getPoolSize()); 097 098 ExecutorStatus status = executor.getStatus(); 099 assertTrue(status.queuedEvents.isEmpty()); 100 assertEquals(5, status.running.size()); 101 Waiter.waitFor(mockedServer.getConfiguration(), 10000, 102 () -> checkStatusDump(executor.getStatus())); 103 104 // Now interrupt the running Executor 105 synchronized (lock) { 106 lock.set(false); 107 lock.notifyAll(); 108 } 109 110 // Executor increments counter again on way out so.... test that happened. 111 while (counter.get() < (maxThreads * 2) && tries < maxTries) { 112 System.out.println("Waiting for all event handlers to finish..."); 113 Thread.sleep(sleepInterval); 114 tries++; 115 } 116 117 assertEquals(maxThreads * 2, counter.get()); 118 assertEquals(maxThreads, pool.getPoolSize()); 119 120 // Add more than the number of threads items. 121 // Make sure we don't get RejectedExecutionException. 122 for (int i = 0; i < (2 * maxThreads); i++) { 123 executorService 124 .submit(new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN, lock, counter)); 125 } 126 // Now interrupt the running Executor 127 synchronized (lock) { 128 lock.set(false); 129 lock.notifyAll(); 130 } 131 132 // Make sure threads are still around even after their timetolive expires. 133 Thread.sleep(ExecutorConfig.KEEP_ALIVE_TIME_MILLIS_DEFAULT * 2); 134 assertEquals(maxThreads, pool.getPoolSize()); 135 136 executorService.shutdown(); 137 138 assertEquals(0, executorService.getAllExecutorStatuses().size()); 139 140 // Test that submit doesn't throw NPEs 141 executorService 142 .submit(new TestEventHandler(mockedServer, EventType.M_SERVER_SHUTDOWN, lock, counter)); 143 } 144 145 private boolean checkStatusDump(ExecutorStatus status) throws IOException { 146 StringWriter sw = new StringWriter(); 147 status.dumpTo(sw, ""); 148 String dump = sw.toString(); 149 LOG.info("Got status dump:\n" + dump); 150 151 return dump.contains("Waiting on java.util.concurrent.atomic.AtomicBoolean"); 152 } 153 154 public static class TestEventHandler extends EventHandler { 155 private final AtomicBoolean lock; 156 private AtomicInteger counter; 157 158 public TestEventHandler(Server server, EventType eventType, AtomicBoolean lock, 159 AtomicInteger counter) { 160 super(server, eventType); 161 this.lock = lock; 162 this.counter = counter; 163 } 164 165 @Override 166 public void process() throws IOException { 167 int num = counter.incrementAndGet(); 168 LOG.info("Running process #" + num + ", threadName=" + Thread.currentThread().getName()); 169 synchronized (lock) { 170 while (lock.get()) { 171 try { 172 lock.wait(); 173 } catch (InterruptedException e) { 174 // do nothing 175 } 176 } 177 } 178 counter.incrementAndGet(); 179 } 180 } 181 182 @Test 183 public void testAborting() throws Exception { 184 final Configuration conf = HBaseConfiguration.create(); 185 final Server server = mock(Server.class); 186 when(server.getConfiguration()).thenReturn(conf); 187 188 ExecutorService executorService = new ExecutorService("unit_test"); 189 executorService.startExecutorService(executorService.new ExecutorConfig() 190 .setExecutorType(ExecutorType.MASTER_SERVER_OPERATIONS).setCorePoolSize(1)); 191 192 executorService.submit(new EventHandler(server, EventType.M_SERVER_SHUTDOWN) { 193 @Override 194 public void process() throws IOException { 195 throw new RuntimeException("Should cause abort"); 196 } 197 }); 198 199 Waiter.waitFor(conf, 30000, new Predicate<Exception>() { 200 @Override 201 public boolean evaluate() throws Exception { 202 try { 203 verify(server, times(1)).abort(anyString(), any()); 204 return true; 205 } catch (Throwable t) { 206 return false; 207 } 208 } 209 }); 210 211 executorService.shutdown(); 212 } 213 214 @Test 215 public void testSnapshotHandlers() throws Exception { 216 final Configuration conf = HBaseConfiguration.create(); 217 final Server server = mock(Server.class); 218 when(server.getConfiguration()).thenReturn(conf); 219 220 ExecutorService executorService = new ExecutorService("testSnapshotHandlers"); 221 executorService.startExecutorService(executorService.new ExecutorConfig() 222 .setExecutorType(ExecutorType.MASTER_SNAPSHOT_OPERATIONS).setCorePoolSize(1)); 223 224 CountDownLatch latch = new CountDownLatch(1); 225 CountDownLatch waitForEventToStart = new CountDownLatch(1); 226 executorService.submit(new EventHandler(server, EventType.C_M_SNAPSHOT_TABLE) { 227 @Override 228 public void process() throws IOException { 229 waitForEventToStart.countDown(); 230 try { 231 latch.await(); 232 } catch (InterruptedException e) { 233 Thread.currentThread().interrupt(); 234 } 235 } 236 }); 237 238 // Wait EventHandler to start 239 waitForEventToStart.await(10, TimeUnit.SECONDS); 240 int activeCount = executorService.getExecutor(ExecutorType.MASTER_SNAPSHOT_OPERATIONS) 241 .getThreadPoolExecutor().getActiveCount(); 242 assertEquals(1, activeCount); 243 latch.countDown(); 244 Waiter.waitFor(conf, 3000, () -> { 245 int count = executorService.getExecutor(ExecutorType.MASTER_SNAPSHOT_OPERATIONS) 246 .getThreadPoolExecutor().getActiveCount(); 247 return count == 0; 248 }); 249 } 250}