1 /* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.util.concurrent; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.junit.Assert.assertThrows; 21 22 import com.google.common.testing.TearDown; 23 import com.google.common.testing.TearDownStack; 24 import com.google.common.util.concurrent.testing.TestingExecutors; 25 import java.lang.Thread.UncaughtExceptionHandler; 26 import java.util.concurrent.CountDownLatch; 27 import java.util.concurrent.Executor; 28 import java.util.concurrent.ExecutorService; 29 import java.util.concurrent.Executors; 30 import java.util.concurrent.ScheduledExecutorService; 31 import java.util.concurrent.TimeUnit; 32 import java.util.concurrent.TimeoutException; 33 import junit.framework.TestCase; 34 35 /** 36 * Unit test for {@link AbstractExecutionThreadService}. 37 * 38 * @author Jesse Wilson 39 */ 40 public class AbstractExecutionThreadServiceTest extends TestCase { 41 42 private final TearDownStack tearDownStack = new TearDownStack(true); 43 private final CountDownLatch enterRun = new CountDownLatch(1); 44 private final CountDownLatch exitRun = new CountDownLatch(1); 45 46 private Thread executionThread; 47 private Throwable thrownByExecutionThread; 48 private final Executor exceptionCatchingExecutor = 49 new Executor() { 50 @Override 51 public void execute(Runnable command) { 52 executionThread = new Thread(command); 53 executionThread.setUncaughtExceptionHandler( 54 new UncaughtExceptionHandler() { 55 @Override 56 public void uncaughtException(Thread thread, Throwable e) { 57 thrownByExecutionThread = e; 58 } 59 }); 60 executionThread.start(); 61 } 62 }; 63 64 @Override tearDown()65 protected final void tearDown() { 66 tearDownStack.runTearDown(); 67 assertNull( 68 "exceptions should not be propagated to uncaught exception handlers", 69 thrownByExecutionThread); 70 } 71 testServiceStartStop()72 public void testServiceStartStop() throws Exception { 73 WaitOnRunService service = new WaitOnRunService(); 74 assertFalse(service.startUpCalled); 75 76 service.startAsync().awaitRunning(); 77 assertTrue(service.startUpCalled); 78 assertEquals(Service.State.RUNNING, service.state()); 79 80 enterRun.await(); // to avoid stopping the service until run() is invoked 81 82 service.stopAsync().awaitTerminated(); 83 assertTrue(service.shutDownCalled); 84 assertEquals(Service.State.TERMINATED, service.state()); 85 executionThread.join(); 86 } 87 testServiceStopIdempotence()88 public void testServiceStopIdempotence() throws Exception { 89 WaitOnRunService service = new WaitOnRunService(); 90 91 service.startAsync().awaitRunning(); 92 enterRun.await(); // to avoid stopping the service until run() is invoked 93 94 service.stopAsync(); 95 service.stopAsync(); 96 service.stopAsync().awaitTerminated(); 97 assertEquals(Service.State.TERMINATED, service.state()); 98 service.stopAsync().awaitTerminated(); 99 assertEquals(Service.State.TERMINATED, service.state()); 100 101 executionThread.join(); 102 } 103 testServiceExitingOnItsOwn()104 public void testServiceExitingOnItsOwn() throws Exception { 105 WaitOnRunService service = new WaitOnRunService(); 106 service.expectedShutdownState = Service.State.RUNNING; 107 108 service.startAsync().awaitRunning(); 109 assertTrue(service.startUpCalled); 110 assertEquals(Service.State.RUNNING, service.state()); 111 112 exitRun.countDown(); // the service will exit voluntarily 113 executionThread.join(); 114 115 assertTrue(service.shutDownCalled); 116 assertEquals(Service.State.TERMINATED, service.state()); 117 118 service.stopAsync().awaitTerminated(); // no-op 119 assertEquals(Service.State.TERMINATED, service.state()); 120 assertTrue(service.shutDownCalled); 121 } 122 123 private class WaitOnRunService extends AbstractExecutionThreadService { 124 private boolean startUpCalled = false; 125 private boolean runCalled = false; 126 private boolean shutDownCalled = false; 127 private State expectedShutdownState = State.STOPPING; 128 129 @Override startUp()130 protected void startUp() { 131 assertFalse(startUpCalled); 132 assertFalse(runCalled); 133 assertFalse(shutDownCalled); 134 startUpCalled = true; 135 assertEquals(State.STARTING, state()); 136 } 137 138 @Override run()139 protected void run() { 140 assertTrue(startUpCalled); 141 assertFalse(runCalled); 142 assertFalse(shutDownCalled); 143 runCalled = true; 144 assertEquals(State.RUNNING, state()); 145 146 enterRun.countDown(); 147 try { 148 exitRun.await(); 149 } catch (InterruptedException e) { 150 throw new RuntimeException(e); 151 } 152 } 153 154 @Override shutDown()155 protected void shutDown() { 156 assertTrue(startUpCalled); 157 assertTrue(runCalled); 158 assertFalse(shutDownCalled); 159 shutDownCalled = true; 160 assertEquals(expectedShutdownState, state()); 161 } 162 163 @Override triggerShutdown()164 protected void triggerShutdown() { 165 exitRun.countDown(); 166 } 167 168 @Override executor()169 protected Executor executor() { 170 return exceptionCatchingExecutor; 171 } 172 } 173 testServiceThrowOnStartUp()174 public void testServiceThrowOnStartUp() throws Exception { 175 ThrowOnStartUpService service = new ThrowOnStartUpService(); 176 assertFalse(service.startUpCalled); 177 178 service.startAsync(); 179 IllegalStateException expected = 180 assertThrows(IllegalStateException.class, () -> service.awaitRunning()); 181 assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); 182 executionThread.join(); 183 184 assertTrue(service.startUpCalled); 185 assertEquals(Service.State.FAILED, service.state()); 186 assertThat(service.failureCause()).hasMessageThat().isEqualTo("kaboom!"); 187 } 188 189 private class ThrowOnStartUpService extends AbstractExecutionThreadService { 190 private boolean startUpCalled = false; 191 192 @Override startUp()193 protected void startUp() { 194 startUpCalled = true; 195 throw new UnsupportedOperationException("kaboom!"); 196 } 197 198 @Override run()199 protected void run() { 200 throw new AssertionError("run() should not be called"); 201 } 202 203 @Override executor()204 protected Executor executor() { 205 return exceptionCatchingExecutor; 206 } 207 } 208 testServiceThrowOnRun()209 public void testServiceThrowOnRun() throws Exception { 210 ThrowOnRunService service = new ThrowOnRunService(); 211 212 service.startAsync(); 213 IllegalStateException expected = 214 assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); 215 executionThread.join(); 216 assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); 217 assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); 218 assertTrue(service.shutDownCalled); 219 assertEquals(Service.State.FAILED, service.state()); 220 } 221 testServiceThrowOnRunAndThenAgainOnShutDown()222 public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception { 223 ThrowOnRunService service = new ThrowOnRunService(); 224 service.throwOnShutDown = true; 225 226 service.startAsync(); 227 IllegalStateException expected = 228 assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); 229 executionThread.join(); 230 assertThat(expected).hasCauseThat().isEqualTo(service.failureCause()); 231 assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!"); 232 assertTrue(service.shutDownCalled); 233 assertEquals(Service.State.FAILED, service.state()); 234 } 235 236 private class ThrowOnRunService extends AbstractExecutionThreadService { 237 private boolean shutDownCalled = false; 238 private boolean throwOnShutDown = false; 239 240 @Override run()241 protected void run() { 242 throw new UnsupportedOperationException("kaboom!"); 243 } 244 245 @Override shutDown()246 protected void shutDown() { 247 shutDownCalled = true; 248 if (throwOnShutDown) { 249 throw new UnsupportedOperationException("double kaboom!"); 250 } 251 } 252 253 @Override executor()254 protected Executor executor() { 255 return exceptionCatchingExecutor; 256 } 257 } 258 testServiceThrowOnShutDown()259 public void testServiceThrowOnShutDown() throws Exception { 260 ThrowOnShutDown service = new ThrowOnShutDown(); 261 262 service.startAsync().awaitRunning(); 263 assertEquals(Service.State.RUNNING, service.state()); 264 265 service.stopAsync(); 266 enterRun.countDown(); 267 executionThread.join(); 268 269 assertEquals(Service.State.FAILED, service.state()); 270 assertThat(service.failureCause()).hasMessageThat().isEqualTo("kaboom!"); 271 } 272 273 private class ThrowOnShutDown extends AbstractExecutionThreadService { 274 @Override run()275 protected void run() { 276 try { 277 enterRun.await(); 278 } catch (InterruptedException e) { 279 throw new RuntimeException(e); 280 } 281 } 282 283 @Override shutDown()284 protected void shutDown() { 285 throw new UnsupportedOperationException("kaboom!"); 286 } 287 288 @Override executor()289 protected Executor executor() { 290 return exceptionCatchingExecutor; 291 } 292 } 293 testServiceTimeoutOnStartUp()294 public void testServiceTimeoutOnStartUp() throws Exception { 295 TimeoutOnStartUp service = new TimeoutOnStartUp(); 296 297 TimeoutException e = 298 assertThrows( 299 TimeoutException.class, 300 () -> service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS)); 301 assertThat(e.getMessage()).contains(Service.State.STARTING.toString()); 302 } 303 304 private class TimeoutOnStartUp extends AbstractExecutionThreadService { 305 @Override executor()306 protected Executor executor() { 307 return new Executor() { 308 @Override 309 public void execute(Runnable command) {} 310 }; 311 } 312 313 @Override run()314 protected void run() throws Exception {} 315 } 316 testStopWhileStarting_runNotCalled()317 public void testStopWhileStarting_runNotCalled() throws Exception { 318 final CountDownLatch started = new CountDownLatch(1); 319 FakeService service = 320 new FakeService() { 321 @Override 322 protected void startUp() throws Exception { 323 super.startUp(); 324 started.await(); 325 } 326 }; 327 service.startAsync(); 328 service.stopAsync(); 329 started.countDown(); 330 service.awaitTerminated(); 331 assertEquals(Service.State.TERMINATED, service.state()); 332 assertEquals(1, service.startupCalled); 333 assertEquals(0, service.runCalled); 334 assertEquals(1, service.shutdownCalled); 335 } 336 testStop_noStart()337 public void testStop_noStart() { 338 FakeService service = new FakeService(); 339 service.stopAsync().awaitTerminated(); 340 assertEquals(Service.State.TERMINATED, service.state()); 341 assertEquals(0, service.startupCalled); 342 assertEquals(0, service.runCalled); 343 assertEquals(0, service.shutdownCalled); 344 } 345 testDefaultService()346 public void testDefaultService() throws InterruptedException { 347 WaitOnRunService service = new WaitOnRunService(); 348 service.startAsync().awaitRunning(); 349 enterRun.await(); 350 service.stopAsync().awaitTerminated(); 351 } 352 testTimeout()353 public void testTimeout() { 354 // Create a service whose executor will never run its commands 355 Service service = 356 new AbstractExecutionThreadService() { 357 @Override 358 protected void run() throws Exception {} 359 360 @Override 361 protected ScheduledExecutorService executor() { 362 return TestingExecutors.noOpScheduledExecutor(); 363 } 364 365 @Override 366 protected String serviceName() { 367 return "Foo"; 368 } 369 }; 370 TimeoutException e = 371 assertThrows( 372 TimeoutException.class, 373 () -> service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS)); 374 assertThat(e) 375 .hasMessageThat() 376 .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state."); 377 } 378 379 private class FakeService extends AbstractExecutionThreadService implements TearDown { 380 381 private final ExecutorService executor = Executors.newSingleThreadExecutor(); 382 FakeService()383 FakeService() { 384 tearDownStack.addTearDown(this); 385 } 386 387 volatile int startupCalled = 0; 388 volatile int shutdownCalled = 0; 389 volatile int runCalled = 0; 390 391 @Override startUp()392 protected void startUp() throws Exception { 393 assertEquals(0, startupCalled); 394 assertEquals(0, runCalled); 395 assertEquals(0, shutdownCalled); 396 startupCalled++; 397 } 398 399 @Override run()400 protected void run() throws Exception { 401 assertEquals(1, startupCalled); 402 assertEquals(0, runCalled); 403 assertEquals(0, shutdownCalled); 404 runCalled++; 405 } 406 407 @Override shutDown()408 protected void shutDown() throws Exception { 409 assertEquals(1, startupCalled); 410 assertEquals(0, shutdownCalled); 411 assertEquals(Service.State.STOPPING, state()); 412 shutdownCalled++; 413 } 414 415 @Override executor()416 protected Executor executor() { 417 return executor; 418 } 419 420 @Override tearDown()421 public void tearDown() throws Exception { 422 executor.shutdown(); 423 } 424 } 425 } 426