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