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