View Javadoc
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 }