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