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 com.google.common.util.concurrent.MoreExecutors.directExecutor; 21 import static java.lang.Thread.currentThread; 22 import static java.util.concurrent.TimeUnit.SECONDS; 23 import static org.junit.Assert.assertThrows; 24 25 import com.google.common.collect.ImmutableList; 26 import com.google.common.collect.Iterables; 27 import com.google.common.collect.Lists; 28 import com.google.common.util.concurrent.Service.Listener; 29 import com.google.common.util.concurrent.Service.State; 30 import com.google.errorprone.annotations.concurrent.GuardedBy; 31 import java.lang.Thread.UncaughtExceptionHandler; 32 import java.util.List; 33 import java.util.concurrent.CountDownLatch; 34 import java.util.concurrent.TimeUnit; 35 import java.util.concurrent.atomic.AtomicInteger; 36 import java.util.concurrent.atomic.AtomicReference; 37 import junit.framework.TestCase; 38 39 /** 40 * Unit test for {@link AbstractService}. 41 * 42 * @author Jesse Wilson 43 */ 44 public class AbstractServiceTest extends TestCase { 45 46 private static final long LONG_TIMEOUT_MILLIS = 10000; 47 private Thread executionThread; 48 private Throwable thrownByExecutionThread; 49 testNoOpServiceStartStop()50 public void testNoOpServiceStartStop() throws Exception { 51 NoOpService service = new NoOpService(); 52 RecordingListener listener = RecordingListener.record(service); 53 54 assertEquals(State.NEW, service.state()); 55 assertFalse(service.isRunning()); 56 assertFalse(service.running); 57 58 service.startAsync(); 59 assertEquals(State.RUNNING, service.state()); 60 assertTrue(service.isRunning()); 61 assertTrue(service.running); 62 63 service.stopAsync(); 64 assertEquals(State.TERMINATED, service.state()); 65 assertFalse(service.isRunning()); 66 assertFalse(service.running); 67 assertEquals( 68 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED), 69 listener.getStateHistory()); 70 } 71 testNoOpServiceStartAndWaitStopAndWait()72 public void testNoOpServiceStartAndWaitStopAndWait() throws Exception { 73 NoOpService service = new NoOpService(); 74 75 service.startAsync().awaitRunning(); 76 assertEquals(State.RUNNING, service.state()); 77 78 service.stopAsync().awaitTerminated(); 79 assertEquals(State.TERMINATED, service.state()); 80 } 81 testNoOpServiceStartAsyncAndAwaitStopAsyncAndAwait()82 public void testNoOpServiceStartAsyncAndAwaitStopAsyncAndAwait() throws Exception { 83 NoOpService service = new NoOpService(); 84 85 service.startAsync().awaitRunning(); 86 assertEquals(State.RUNNING, service.state()); 87 88 service.stopAsync().awaitTerminated(); 89 assertEquals(State.TERMINATED, service.state()); 90 } 91 testNoOpServiceStopIdempotence()92 public void testNoOpServiceStopIdempotence() throws Exception { 93 NoOpService service = new NoOpService(); 94 RecordingListener listener = RecordingListener.record(service); 95 service.startAsync().awaitRunning(); 96 assertEquals(State.RUNNING, service.state()); 97 98 service.stopAsync(); 99 service.stopAsync(); 100 assertEquals(State.TERMINATED, service.state()); 101 assertEquals( 102 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED), 103 listener.getStateHistory()); 104 } 105 testNoOpServiceStopIdempotenceAfterWait()106 public void testNoOpServiceStopIdempotenceAfterWait() throws Exception { 107 NoOpService service = new NoOpService(); 108 109 service.startAsync().awaitRunning(); 110 111 service.stopAsync().awaitTerminated(); 112 service.stopAsync(); 113 assertEquals(State.TERMINATED, service.state()); 114 } 115 testNoOpServiceStopIdempotenceDoubleWait()116 public void testNoOpServiceStopIdempotenceDoubleWait() throws Exception { 117 NoOpService service = new NoOpService(); 118 119 service.startAsync().awaitRunning(); 120 assertEquals(State.RUNNING, service.state()); 121 122 service.stopAsync().awaitTerminated(); 123 service.stopAsync().awaitTerminated(); 124 assertEquals(State.TERMINATED, service.state()); 125 } 126 testNoOpServiceStartStopAndWaitUninterruptible()127 public void testNoOpServiceStartStopAndWaitUninterruptible() throws Exception { 128 NoOpService service = new NoOpService(); 129 130 currentThread().interrupt(); 131 try { 132 service.startAsync().awaitRunning(); 133 assertEquals(State.RUNNING, service.state()); 134 135 service.stopAsync().awaitTerminated(); 136 assertEquals(State.TERMINATED, service.state()); 137 138 assertTrue(currentThread().isInterrupted()); 139 } finally { 140 Thread.interrupted(); // clear interrupt for future tests 141 } 142 } 143 144 private static class NoOpService extends AbstractService { 145 boolean running = false; 146 147 @Override doStart()148 protected void doStart() { 149 assertFalse(running); 150 running = true; 151 notifyStarted(); 152 } 153 154 @Override doStop()155 protected void doStop() { 156 assertTrue(running); 157 running = false; 158 notifyStopped(); 159 } 160 } 161 testManualServiceStartStop()162 public void testManualServiceStartStop() throws Exception { 163 ManualSwitchedService service = new ManualSwitchedService(); 164 RecordingListener listener = RecordingListener.record(service); 165 166 service.startAsync(); 167 assertEquals(State.STARTING, service.state()); 168 assertFalse(service.isRunning()); 169 assertTrue(service.doStartCalled); 170 171 service.notifyStarted(); // usually this would be invoked by another thread 172 assertEquals(State.RUNNING, service.state()); 173 assertTrue(service.isRunning()); 174 175 service.stopAsync(); 176 assertEquals(State.STOPPING, service.state()); 177 assertFalse(service.isRunning()); 178 assertTrue(service.doStopCalled); 179 180 service.notifyStopped(); // usually this would be invoked by another thread 181 assertEquals(State.TERMINATED, service.state()); 182 assertFalse(service.isRunning()); 183 assertEquals( 184 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED), 185 listener.getStateHistory()); 186 } 187 testManualServiceNotifyStoppedWhileRunning()188 public void testManualServiceNotifyStoppedWhileRunning() throws Exception { 189 ManualSwitchedService service = new ManualSwitchedService(); 190 RecordingListener listener = RecordingListener.record(service); 191 192 service.startAsync(); 193 service.notifyStarted(); 194 service.notifyStopped(); 195 assertEquals(State.TERMINATED, service.state()); 196 assertFalse(service.isRunning()); 197 assertFalse(service.doStopCalled); 198 199 assertEquals( 200 ImmutableList.of(State.STARTING, State.RUNNING, State.TERMINATED), 201 listener.getStateHistory()); 202 } 203 testManualServiceStopWhileStarting()204 public void testManualServiceStopWhileStarting() throws Exception { 205 ManualSwitchedService service = new ManualSwitchedService(); 206 RecordingListener listener = RecordingListener.record(service); 207 208 service.startAsync(); 209 assertEquals(State.STARTING, service.state()); 210 assertFalse(service.isRunning()); 211 assertTrue(service.doStartCalled); 212 213 service.stopAsync(); 214 assertEquals(State.STOPPING, service.state()); 215 assertFalse(service.isRunning()); 216 assertFalse(service.doStopCalled); 217 218 service.notifyStarted(); 219 assertEquals(State.STOPPING, service.state()); 220 assertFalse(service.isRunning()); 221 assertTrue(service.doStopCalled); 222 223 service.notifyStopped(); 224 assertEquals(State.TERMINATED, service.state()); 225 assertFalse(service.isRunning()); 226 assertEquals( 227 ImmutableList.of(State.STARTING, State.STOPPING, State.TERMINATED), 228 listener.getStateHistory()); 229 } 230 231 /** 232 * This tests for a bug where if {@link Service#stopAsync()} was called while the service was 233 * {@link State#STARTING} more than once, the {@link Listener#stopping(State)} callback would get 234 * called multiple times. 235 */ testManualServiceStopMultipleTimesWhileStarting()236 public void testManualServiceStopMultipleTimesWhileStarting() throws Exception { 237 ManualSwitchedService service = new ManualSwitchedService(); 238 final AtomicInteger stoppingCount = new AtomicInteger(); 239 service.addListener( 240 new Listener() { 241 @Override 242 public void stopping(State from) { 243 stoppingCount.incrementAndGet(); 244 } 245 }, 246 directExecutor()); 247 248 service.startAsync(); 249 service.stopAsync(); 250 assertEquals(1, stoppingCount.get()); 251 service.stopAsync(); 252 assertEquals(1, stoppingCount.get()); 253 } 254 testManualServiceStopWhileNew()255 public void testManualServiceStopWhileNew() throws Exception { 256 ManualSwitchedService service = new ManualSwitchedService(); 257 RecordingListener listener = RecordingListener.record(service); 258 259 service.stopAsync(); 260 assertEquals(State.TERMINATED, service.state()); 261 assertFalse(service.isRunning()); 262 assertFalse(service.doStartCalled); 263 assertFalse(service.doStopCalled); 264 assertEquals(ImmutableList.of(State.TERMINATED), listener.getStateHistory()); 265 } 266 testManualServiceFailWhileStarting()267 public void testManualServiceFailWhileStarting() throws Exception { 268 ManualSwitchedService service = new ManualSwitchedService(); 269 RecordingListener listener = RecordingListener.record(service); 270 service.startAsync(); 271 service.notifyFailed(EXCEPTION); 272 assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); 273 } 274 testManualServiceFailWhileRunning()275 public void testManualServiceFailWhileRunning() throws Exception { 276 ManualSwitchedService service = new ManualSwitchedService(); 277 RecordingListener listener = RecordingListener.record(service); 278 service.startAsync(); 279 service.notifyStarted(); 280 service.notifyFailed(EXCEPTION); 281 assertEquals( 282 ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory()); 283 } 284 testManualServiceFailWhileStopping()285 public void testManualServiceFailWhileStopping() throws Exception { 286 ManualSwitchedService service = new ManualSwitchedService(); 287 RecordingListener listener = RecordingListener.record(service); 288 service.startAsync(); 289 service.notifyStarted(); 290 service.stopAsync(); 291 service.notifyFailed(EXCEPTION); 292 assertEquals( 293 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), 294 listener.getStateHistory()); 295 } 296 testManualServiceUnrequestedStop()297 public void testManualServiceUnrequestedStop() { 298 ManualSwitchedService service = new ManualSwitchedService(); 299 300 service.startAsync(); 301 302 service.notifyStarted(); 303 assertEquals(State.RUNNING, service.state()); 304 assertTrue(service.isRunning()); 305 assertFalse(service.doStopCalled); 306 307 service.notifyStopped(); 308 assertEquals(State.TERMINATED, service.state()); 309 assertFalse(service.isRunning()); 310 assertFalse(service.doStopCalled); 311 } 312 313 /** 314 * The user of this service should call {@link #notifyStarted} and {@link #notifyStopped} after 315 * calling {@link #startAsync} and {@link #stopAsync}. 316 */ 317 private static class ManualSwitchedService extends AbstractService { 318 boolean doStartCalled = false; 319 boolean doStopCalled = false; 320 321 @Override doStart()322 protected void doStart() { 323 assertFalse(doStartCalled); 324 doStartCalled = true; 325 } 326 327 @Override doStop()328 protected void doStop() { 329 assertFalse(doStopCalled); 330 doStopCalled = true; 331 } 332 } 333 testAwaitTerminated()334 public void testAwaitTerminated() throws Exception { 335 final NoOpService service = new NoOpService(); 336 Thread waiter = 337 new Thread() { 338 @Override 339 public void run() { 340 service.awaitTerminated(); 341 } 342 }; 343 waiter.start(); 344 service.startAsync().awaitRunning(); 345 assertEquals(State.RUNNING, service.state()); 346 service.stopAsync(); 347 waiter.join(LONG_TIMEOUT_MILLIS); // ensure that the await in the other thread is triggered 348 assertFalse(waiter.isAlive()); 349 } 350 testAwaitTerminated_failedService()351 public void testAwaitTerminated_failedService() throws Exception { 352 final ManualSwitchedService service = new ManualSwitchedService(); 353 final AtomicReference<Throwable> exception = Atomics.newReference(); 354 Thread waiter = 355 new Thread() { 356 @Override 357 public void run() { 358 try { 359 service.awaitTerminated(); 360 fail("Expected an IllegalStateException"); 361 } catch (Throwable t) { 362 exception.set(t); 363 } 364 } 365 }; 366 waiter.start(); 367 service.startAsync(); 368 service.notifyStarted(); 369 assertEquals(State.RUNNING, service.state()); 370 service.notifyFailed(EXCEPTION); 371 assertEquals(State.FAILED, service.state()); 372 waiter.join(LONG_TIMEOUT_MILLIS); 373 assertFalse(waiter.isAlive()); 374 assertThat(exception.get()).isInstanceOf(IllegalStateException.class); 375 assertThat(exception.get()).hasCauseThat().isEqualTo(EXCEPTION); 376 } 377 testThreadedServiceStartAndWaitStopAndWait()378 public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable { 379 ThreadedService service = new ThreadedService(); 380 RecordingListener listener = RecordingListener.record(service); 381 service.startAsync().awaitRunning(); 382 assertEquals(State.RUNNING, service.state()); 383 384 service.awaitRunChecks(); 385 386 service.stopAsync().awaitTerminated(); 387 assertEquals(State.TERMINATED, service.state()); 388 389 throwIfSet(thrownByExecutionThread); 390 assertEquals( 391 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.TERMINATED), 392 listener.getStateHistory()); 393 } 394 testThreadedServiceStopIdempotence()395 public void testThreadedServiceStopIdempotence() throws Throwable { 396 ThreadedService service = new ThreadedService(); 397 398 service.startAsync().awaitRunning(); 399 assertEquals(State.RUNNING, service.state()); 400 401 service.awaitRunChecks(); 402 403 service.stopAsync(); 404 service.stopAsync().awaitTerminated(); 405 assertEquals(State.TERMINATED, service.state()); 406 407 throwIfSet(thrownByExecutionThread); 408 } 409 testThreadedServiceStopIdempotenceAfterWait()410 public void testThreadedServiceStopIdempotenceAfterWait() throws Throwable { 411 ThreadedService service = new ThreadedService(); 412 413 service.startAsync().awaitRunning(); 414 assertEquals(State.RUNNING, service.state()); 415 416 service.awaitRunChecks(); 417 418 service.stopAsync().awaitTerminated(); 419 service.stopAsync(); 420 assertEquals(State.TERMINATED, service.state()); 421 422 executionThread.join(); 423 424 throwIfSet(thrownByExecutionThread); 425 } 426 testThreadedServiceStopIdempotenceDoubleWait()427 public void testThreadedServiceStopIdempotenceDoubleWait() throws Throwable { 428 ThreadedService service = new ThreadedService(); 429 430 service.startAsync().awaitRunning(); 431 assertEquals(State.RUNNING, service.state()); 432 433 service.awaitRunChecks(); 434 435 service.stopAsync().awaitTerminated(); 436 service.stopAsync().awaitTerminated(); 437 assertEquals(State.TERMINATED, service.state()); 438 439 throwIfSet(thrownByExecutionThread); 440 } 441 testManualServiceFailureIdempotence()442 public void testManualServiceFailureIdempotence() { 443 ManualSwitchedService service = new ManualSwitchedService(); 444 /* 445 * Set up a RecordingListener to perform its built-in assertions, even though we won't look at 446 * its state history. 447 */ 448 RecordingListener unused = RecordingListener.record(service); 449 service.startAsync(); 450 service.notifyFailed(new Exception("1")); 451 service.notifyFailed(new Exception("2")); 452 assertThat(service.failureCause()).hasMessageThat().isEqualTo("1"); 453 IllegalStateException e = 454 assertThrows(IllegalStateException.class, () -> service.awaitRunning()); 455 assertThat(e).hasCauseThat().hasMessageThat().isEqualTo("1"); 456 } 457 458 private class ThreadedService extends AbstractService { 459 final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1); 460 461 /* 462 * The main test thread tries to stop() the service shortly after 463 * confirming that it is running. Meanwhile, the service itself is trying 464 * to confirm that it is running. If the main thread's stop() call happens 465 * before it has the chance, the test will fail. To avoid this, the main 466 * thread calls this method, which waits until the service has performed 467 * its own "running" check. 468 */ awaitRunChecks()469 void awaitRunChecks() throws InterruptedException { 470 assertTrue( 471 "Service thread hasn't finished its checks. " 472 + "Exception status (possibly stale): " 473 + thrownByExecutionThread, 474 hasConfirmedIsRunning.await(10, SECONDS)); 475 } 476 477 @Override doStart()478 protected void doStart() { 479 assertEquals(State.STARTING, state()); 480 invokeOnExecutionThreadForTest( 481 new Runnable() { 482 @Override 483 public void run() { 484 assertEquals(State.STARTING, state()); 485 notifyStarted(); 486 assertEquals(State.RUNNING, state()); 487 hasConfirmedIsRunning.countDown(); 488 } 489 }); 490 } 491 492 @Override doStop()493 protected void doStop() { 494 assertEquals(State.STOPPING, state()); 495 invokeOnExecutionThreadForTest( 496 new Runnable() { 497 @Override 498 public void run() { 499 assertEquals(State.STOPPING, state()); 500 notifyStopped(); 501 assertEquals(State.TERMINATED, state()); 502 } 503 }); 504 } 505 } 506 invokeOnExecutionThreadForTest(Runnable runnable)507 private void invokeOnExecutionThreadForTest(Runnable runnable) { 508 executionThread = new Thread(runnable); 509 executionThread.setUncaughtExceptionHandler( 510 new UncaughtExceptionHandler() { 511 @Override 512 public void uncaughtException(Thread thread, Throwable e) { 513 thrownByExecutionThread = e; 514 } 515 }); 516 executionThread.start(); 517 } 518 throwIfSet(Throwable t)519 private static void throwIfSet(Throwable t) throws Throwable { 520 if (t != null) { 521 throw t; 522 } 523 } 524 testStopUnstartedService()525 public void testStopUnstartedService() throws Exception { 526 NoOpService service = new NoOpService(); 527 RecordingListener listener = RecordingListener.record(service); 528 529 service.stopAsync(); 530 assertEquals(State.TERMINATED, service.state()); 531 532 assertThrows(IllegalStateException.class, () -> service.startAsync()); 533 assertEquals(State.TERMINATED, Iterables.getOnlyElement(listener.getStateHistory())); 534 } 535 testFailingServiceStartAndWait()536 public void testFailingServiceStartAndWait() throws Exception { 537 StartFailingService service = new StartFailingService(); 538 RecordingListener listener = RecordingListener.record(service); 539 540 IllegalStateException e = 541 assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); 542 assertEquals(EXCEPTION, service.failureCause()); 543 assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); 544 assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); 545 } 546 testFailingServiceStopAndWait_stopFailing()547 public void testFailingServiceStopAndWait_stopFailing() throws Exception { 548 StopFailingService service = new StopFailingService(); 549 RecordingListener listener = RecordingListener.record(service); 550 551 service.startAsync().awaitRunning(); 552 IllegalStateException e = 553 assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); 554 assertEquals(EXCEPTION, service.failureCause()); 555 assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); 556 assertEquals( 557 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), 558 listener.getStateHistory()); 559 } 560 testFailingServiceStopAndWait_runFailing()561 public void testFailingServiceStopAndWait_runFailing() throws Exception { 562 RunFailingService service = new RunFailingService(); 563 RecordingListener listener = RecordingListener.record(service); 564 565 service.startAsync(); 566 IllegalStateException e = 567 assertThrows(IllegalStateException.class, () -> service.awaitRunning()); 568 assertEquals(EXCEPTION, service.failureCause()); 569 assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); 570 assertEquals( 571 ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory()); 572 } 573 testThrowingServiceStartAndWait()574 public void testThrowingServiceStartAndWait() throws Exception { 575 StartThrowingService service = new StartThrowingService(); 576 RecordingListener listener = RecordingListener.record(service); 577 578 IllegalStateException e = 579 assertThrows(IllegalStateException.class, () -> service.startAsync().awaitRunning()); 580 assertEquals(service.exception, service.failureCause()); 581 assertThat(e).hasCauseThat().isEqualTo(service.exception); 582 assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory()); 583 } 584 testThrowingServiceStopAndWait_stopThrowing()585 public void testThrowingServiceStopAndWait_stopThrowing() throws Exception { 586 StopThrowingService service = new StopThrowingService(); 587 RecordingListener listener = RecordingListener.record(service); 588 589 service.startAsync().awaitRunning(); 590 IllegalStateException e = 591 assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); 592 assertEquals(service.exception, service.failureCause()); 593 assertThat(e).hasCauseThat().isEqualTo(service.exception); 594 assertEquals( 595 ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED), 596 listener.getStateHistory()); 597 } 598 testThrowingServiceStopAndWait_runThrowing()599 public void testThrowingServiceStopAndWait_runThrowing() throws Exception { 600 RunThrowingService service = new RunThrowingService(); 601 RecordingListener listener = RecordingListener.record(service); 602 603 service.startAsync(); 604 IllegalStateException e = 605 assertThrows(IllegalStateException.class, () -> service.awaitTerminated()); 606 assertEquals(service.exception, service.failureCause()); 607 assertThat(e).hasCauseThat().isEqualTo(service.exception); 608 assertEquals( 609 ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED), listener.getStateHistory()); 610 } 611 testFailureCause_throwsIfNotFailed()612 public void testFailureCause_throwsIfNotFailed() { 613 StopFailingService service = new StopFailingService(); 614 assertThrows(IllegalStateException.class, () -> service.failureCause()); 615 service.startAsync().awaitRunning(); 616 assertThrows(IllegalStateException.class, () -> service.failureCause()); 617 IllegalStateException e = 618 assertThrows(IllegalStateException.class, () -> service.stopAsync().awaitTerminated()); 619 assertEquals(EXCEPTION, service.failureCause()); 620 assertThat(e).hasCauseThat().isEqualTo(EXCEPTION); 621 } 622 testAddListenerAfterFailureDoesntCauseDeadlock()623 public void testAddListenerAfterFailureDoesntCauseDeadlock() throws InterruptedException { 624 final StartFailingService service = new StartFailingService(); 625 service.startAsync(); 626 assertEquals(State.FAILED, service.state()); 627 service.addListener(new RecordingListener(service), directExecutor()); 628 Thread thread = 629 new Thread() { 630 @Override 631 public void run() { 632 // Internally stopAsync() grabs a lock, this could be any such method on 633 // AbstractService. 634 service.stopAsync(); 635 } 636 }; 637 thread.start(); 638 thread.join(LONG_TIMEOUT_MILLIS); 639 assertFalse(thread + " is deadlocked", thread.isAlive()); 640 } 641 testListenerDoesntDeadlockOnStartAndWaitFromRunning()642 public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception { 643 final NoOpThreadedService service = new NoOpThreadedService(); 644 service.addListener( 645 new Listener() { 646 @Override 647 public void running() { 648 service.awaitRunning(); 649 } 650 }, 651 directExecutor()); 652 service.startAsync().awaitRunning(LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 653 service.stopAsync(); 654 } 655 testListenerDoesntDeadlockOnStopAndWaitFromTerminated()656 public void testListenerDoesntDeadlockOnStopAndWaitFromTerminated() throws Exception { 657 final NoOpThreadedService service = new NoOpThreadedService(); 658 service.addListener( 659 new Listener() { 660 @Override 661 public void terminated(State from) { 662 service.stopAsync().awaitTerminated(); 663 } 664 }, 665 directExecutor()); 666 service.startAsync().awaitRunning(); 667 668 Thread thread = 669 new Thread() { 670 @Override 671 public void run() { 672 service.stopAsync().awaitTerminated(); 673 } 674 }; 675 thread.start(); 676 thread.join(LONG_TIMEOUT_MILLIS); 677 assertFalse(thread + " is deadlocked", thread.isAlive()); 678 } 679 680 private static class NoOpThreadedService extends AbstractExecutionThreadService { 681 final CountDownLatch latch = new CountDownLatch(1); 682 683 @Override run()684 protected void run() throws Exception { 685 latch.await(); 686 } 687 688 @Override triggerShutdown()689 protected void triggerShutdown() { 690 latch.countDown(); 691 } 692 } 693 694 private static class StartFailingService extends AbstractService { 695 @Override doStart()696 protected void doStart() { 697 notifyFailed(EXCEPTION); 698 } 699 700 @Override doStop()701 protected void doStop() { 702 fail(); 703 } 704 } 705 706 private static class RunFailingService extends AbstractService { 707 @Override doStart()708 protected void doStart() { 709 notifyStarted(); 710 notifyFailed(EXCEPTION); 711 } 712 713 @Override doStop()714 protected void doStop() { 715 fail(); 716 } 717 } 718 719 private static class StopFailingService extends AbstractService { 720 @Override doStart()721 protected void doStart() { 722 notifyStarted(); 723 } 724 725 @Override doStop()726 protected void doStop() { 727 notifyFailed(EXCEPTION); 728 } 729 } 730 731 private static class StartThrowingService extends AbstractService { 732 733 final RuntimeException exception = new RuntimeException("deliberate"); 734 735 @Override doStart()736 protected void doStart() { 737 throw exception; 738 } 739 740 @Override doStop()741 protected void doStop() { 742 fail(); 743 } 744 } 745 746 private static class RunThrowingService extends AbstractService { 747 748 final RuntimeException exception = new RuntimeException("deliberate"); 749 750 @Override doStart()751 protected void doStart() { 752 notifyStarted(); 753 throw exception; 754 } 755 756 @Override doStop()757 protected void doStop() { 758 fail(); 759 } 760 } 761 762 private static class StopThrowingService extends AbstractService { 763 764 final RuntimeException exception = new RuntimeException("deliberate"); 765 766 @Override doStart()767 protected void doStart() { 768 notifyStarted(); 769 } 770 771 @Override doStop()772 protected void doStop() { 773 throw exception; 774 } 775 } 776 777 private static class RecordingListener extends Listener { record(Service service)778 static RecordingListener record(Service service) { 779 RecordingListener listener = new RecordingListener(service); 780 service.addListener(listener, directExecutor()); 781 return listener; 782 } 783 784 final Service service; 785 RecordingListener(Service service)786 RecordingListener(Service service) { 787 this.service = service; 788 } 789 790 @GuardedBy("this") 791 final List<State> stateHistory = Lists.newArrayList(); 792 793 final CountDownLatch completionLatch = new CountDownLatch(1); 794 getStateHistory()795 ImmutableList<State> getStateHistory() throws Exception { 796 completionLatch.await(); 797 synchronized (this) { 798 return ImmutableList.copyOf(stateHistory); 799 } 800 } 801 802 @Override starting()803 public synchronized void starting() { 804 assertTrue(stateHistory.isEmpty()); 805 assertNotSame(State.NEW, service.state()); 806 stateHistory.add(State.STARTING); 807 } 808 809 @Override running()810 public synchronized void running() { 811 assertEquals(State.STARTING, Iterables.getOnlyElement(stateHistory)); 812 stateHistory.add(State.RUNNING); 813 service.awaitRunning(); 814 assertNotSame(State.STARTING, service.state()); 815 } 816 817 @Override stopping(State from)818 public synchronized void stopping(State from) { 819 assertEquals(from, Iterables.getLast(stateHistory)); 820 stateHistory.add(State.STOPPING); 821 if (from == State.STARTING) { 822 try { 823 service.awaitRunning(); 824 fail(); 825 } catch (IllegalStateException expected) { 826 assertThat(expected).hasCauseThat().isNull(); 827 assertThat(expected) 828 .hasMessageThat() 829 .isEqualTo("Expected the service " + service + " to be RUNNING, but was STOPPING"); 830 } 831 } 832 assertNotSame(from, service.state()); 833 } 834 835 @Override terminated(State from)836 public synchronized void terminated(State from) { 837 assertEquals(from, Iterables.getLast(stateHistory, State.NEW)); 838 stateHistory.add(State.TERMINATED); 839 assertEquals(State.TERMINATED, service.state()); 840 if (from == State.NEW) { 841 try { 842 service.awaitRunning(); 843 fail(); 844 } catch (IllegalStateException expected) { 845 assertThat(expected).hasCauseThat().isNull(); 846 assertThat(expected) 847 .hasMessageThat() 848 .isEqualTo("Expected the service " + service + " to be RUNNING, but was TERMINATED"); 849 } 850 } 851 completionLatch.countDown(); 852 } 853 854 @Override failed(State from, Throwable failure)855 public synchronized void failed(State from, Throwable failure) { 856 assertEquals(from, Iterables.getLast(stateHistory)); 857 stateHistory.add(State.FAILED); 858 assertEquals(State.FAILED, service.state()); 859 assertEquals(failure, service.failureCause()); 860 if (from == State.STARTING) { 861 try { 862 service.awaitRunning(); 863 fail(); 864 } catch (IllegalStateException e) { 865 assertThat(e).hasCauseThat().isEqualTo(failure); 866 } 867 } 868 try { 869 service.awaitTerminated(); 870 fail(); 871 } catch (IllegalStateException e) { 872 assertThat(e).hasCauseThat().isEqualTo(failure); 873 } 874 completionLatch.countDown(); 875 } 876 } 877 testNotifyStartedWhenNotStarting()878 public void testNotifyStartedWhenNotStarting() { 879 AbstractService service = new DefaultService(); 880 assertThrows(IllegalStateException.class, () -> service.notifyStarted()); 881 } 882 testNotifyStoppedWhenNotRunning()883 public void testNotifyStoppedWhenNotRunning() { 884 AbstractService service = new DefaultService(); 885 assertThrows(IllegalStateException.class, () -> service.notifyStopped()); 886 } 887 testNotifyFailedWhenNotStarted()888 public void testNotifyFailedWhenNotStarted() { 889 AbstractService service = new DefaultService(); 890 assertThrows(IllegalStateException.class, () -> service.notifyFailed(new Exception())); 891 } 892 testNotifyFailedWhenTerminated()893 public void testNotifyFailedWhenTerminated() { 894 NoOpService service = new NoOpService(); 895 service.startAsync().awaitRunning(); 896 service.stopAsync().awaitTerminated(); 897 assertThrows(IllegalStateException.class, () -> service.notifyFailed(new Exception())); 898 } 899 900 private static class DefaultService extends AbstractService { 901 @Override doStart()902 protected void doStart() {} 903 904 @Override doStop()905 protected void doStop() {} 906 } 907 908 private static final Exception EXCEPTION = new Exception(); 909 } 910