• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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