Home | History | Annotate | Download | only in concurrent
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    157     @Override protected void doStart() {
    158       assertFalse(running);
    159       running = true;
    160       notifyStarted();
    161     }
    162 
    163     @Override protected void doStop() {
    164       assertTrue(running);
    165       running = false;
    166       notifyStopped();
    167     }
    168   }
    169 
    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 
    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 
    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    */
    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 
    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 
    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 
    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 
    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 
    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 
    336     @Override protected void doStart() {
    337       assertFalse(doStartCalled);
    338       doStartCalled = true;
    339     }
    340 
    341     @Override protected void doStop() {
    342       assertFalse(doStopCalled);
    343       doStopCalled = true;
    344     }
    345   }
    346 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    524   private static void throwIfSet(Throwable t) throws Throwable {
    525     if (t != null) {
    526       throw t;
    527     }
    528   }
    529 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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);
    734     @Override protected void run() throws Exception {
    735       latch.await();
    736     }
    737     @Override protected void triggerShutdown() {
    738       latch.countDown();
    739     }
    740   }
    741 
    742   private static class StartFailingService extends AbstractService {
    743     @Override protected void doStart() {
    744       notifyFailed(EXCEPTION);
    745     }
    746 
    747     @Override protected void doStop() {
    748       fail();
    749     }
    750   }
    751 
    752   private static class RunFailingService extends AbstractService {
    753     @Override protected void doStart() {
    754       notifyStarted();
    755       notifyFailed(EXCEPTION);
    756     }
    757 
    758     @Override protected void doStop() {
    759       fail();
    760     }
    761   }
    762 
    763   private static class StopFailingService extends AbstractService {
    764     @Override protected void doStart() {
    765       notifyStarted();
    766     }
    767 
    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 
    777     @Override protected void doStart() {
    778       throw exception;
    779     }
    780 
    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 
    790     @Override protected void doStart() {
    791       notifyStarted();
    792       throw exception;
    793     }
    794 
    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 
    804     @Override protected void doStart() {
    805       notifyStarted();
    806     }
    807 
    808     @Override protected void doStop() {
    809       throw exception;
    810     }
    811   }
    812 
    813   private static class RecordingListener extends Listener {
    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 
    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 
    830     ImmutableList<State> getStateHistory() throws Exception {
    831       completionLatch.await();
    832       synchronized (this) {
    833         return ImmutableList.copyOf(stateHistory);
    834       }
    835     }
    836 
    837     @Override public synchronized void starting() {
    838       assertTrue(stateHistory.isEmpty());
    839       assertNotSame(State.NEW, service.state());
    840       stateHistory.add(State.STARTING);
    841     }
    842 
    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 
    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 
    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 
    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 
    906   public void testNotifyStartedWhenNotStarting() {
    907     AbstractService service = new DefaultService();
    908     try {
    909       service.notifyStarted();
    910       fail();
    911     } catch (IllegalStateException expected) {}
    912   }
    913 
    914   public void testNotifyStoppedWhenNotRunning() {
    915     AbstractService service = new DefaultService();
    916     try {
    917       service.notifyStopped();
    918       fail();
    919     } catch (IllegalStateException expected) {}
    920   }
    921 
    922   public void testNotifyFailedWhenNotStarted() {
    923     AbstractService service = new DefaultService();
    924     try {
    925       service.notifyFailed(new Exception());
    926       fail();
    927     } catch (IllegalStateException expected) {}
    928   }
    929 
    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 {
    941     @Override protected void doStart() {}
    942     @Override protected void doStop() {}
    943   }
    944 
    945   private static final Exception EXCEPTION = new Exception();
    946 }
    947