• 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 org.junit.Assert.assertThrows;
21 
22 import com.google.common.testing.TearDown;
23 import com.google.common.testing.TearDownStack;
24 import com.google.common.util.concurrent.testing.TestingExecutors;
25 import java.lang.Thread.UncaughtExceptionHandler;
26 import java.util.concurrent.CountDownLatch;
27 import java.util.concurrent.Executor;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 import java.util.concurrent.ScheduledExecutorService;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.TimeoutException;
33 import junit.framework.TestCase;
34 
35 /**
36  * Unit test for {@link AbstractExecutionThreadService}.
37  *
38  * @author Jesse Wilson
39  */
40 public class AbstractExecutionThreadServiceTest extends TestCase {
41 
42   private final TearDownStack tearDownStack = new TearDownStack(true);
43   private final CountDownLatch enterRun = new CountDownLatch(1);
44   private final CountDownLatch exitRun = new CountDownLatch(1);
45 
46   private Thread executionThread;
47   private Throwable thrownByExecutionThread;
48   private final Executor exceptionCatchingExecutor =
49       new Executor() {
50         @Override
51         public void execute(Runnable command) {
52           executionThread = new Thread(command);
53           executionThread.setUncaughtExceptionHandler(
54               new UncaughtExceptionHandler() {
55                 @Override
56                 public void uncaughtException(Thread thread, Throwable e) {
57                   thrownByExecutionThread = e;
58                 }
59               });
60           executionThread.start();
61         }
62       };
63 
64   @Override
tearDown()65   protected final void tearDown() {
66     tearDownStack.runTearDown();
67     assertNull(
68         "exceptions should not be propagated to uncaught exception handlers",
69         thrownByExecutionThread);
70   }
71 
testServiceStartStop()72   public void testServiceStartStop() throws Exception {
73     WaitOnRunService service = new WaitOnRunService();
74     assertFalse(service.startUpCalled);
75 
76     service.startAsync().awaitRunning();
77     assertTrue(service.startUpCalled);
78     assertEquals(Service.State.RUNNING, service.state());
79 
80     enterRun.await(); // to avoid stopping the service until run() is invoked
81 
82     service.stopAsync().awaitTerminated();
83     assertTrue(service.shutDownCalled);
84     assertEquals(Service.State.TERMINATED, service.state());
85     executionThread.join();
86   }
87 
testServiceStopIdempotence()88   public void testServiceStopIdempotence() throws Exception {
89     WaitOnRunService service = new WaitOnRunService();
90 
91     service.startAsync().awaitRunning();
92     enterRun.await(); // to avoid stopping the service until run() is invoked
93 
94     service.stopAsync();
95     service.stopAsync();
96     service.stopAsync().awaitTerminated();
97     assertEquals(Service.State.TERMINATED, service.state());
98     service.stopAsync().awaitTerminated();
99     assertEquals(Service.State.TERMINATED, service.state());
100 
101     executionThread.join();
102   }
103 
testServiceExitingOnItsOwn()104   public void testServiceExitingOnItsOwn() throws Exception {
105     WaitOnRunService service = new WaitOnRunService();
106     service.expectedShutdownState = Service.State.RUNNING;
107 
108     service.startAsync().awaitRunning();
109     assertTrue(service.startUpCalled);
110     assertEquals(Service.State.RUNNING, service.state());
111 
112     exitRun.countDown(); // the service will exit voluntarily
113     executionThread.join();
114 
115     assertTrue(service.shutDownCalled);
116     assertEquals(Service.State.TERMINATED, service.state());
117 
118     service.stopAsync().awaitTerminated(); // no-op
119     assertEquals(Service.State.TERMINATED, service.state());
120     assertTrue(service.shutDownCalled);
121   }
122 
123   private class WaitOnRunService extends AbstractExecutionThreadService {
124     private boolean startUpCalled = false;
125     private boolean runCalled = false;
126     private boolean shutDownCalled = false;
127     private State expectedShutdownState = State.STOPPING;
128 
129     @Override
startUp()130     protected void startUp() {
131       assertFalse(startUpCalled);
132       assertFalse(runCalled);
133       assertFalse(shutDownCalled);
134       startUpCalled = true;
135       assertEquals(State.STARTING, state());
136     }
137 
138     @Override
run()139     protected void run() {
140       assertTrue(startUpCalled);
141       assertFalse(runCalled);
142       assertFalse(shutDownCalled);
143       runCalled = true;
144       assertEquals(State.RUNNING, state());
145 
146       enterRun.countDown();
147       try {
148         exitRun.await();
149       } catch (InterruptedException e) {
150         throw new RuntimeException(e);
151       }
152     }
153 
154     @Override
shutDown()155     protected void shutDown() {
156       assertTrue(startUpCalled);
157       assertTrue(runCalled);
158       assertFalse(shutDownCalled);
159       shutDownCalled = true;
160       assertEquals(expectedShutdownState, state());
161     }
162 
163     @Override
triggerShutdown()164     protected void triggerShutdown() {
165       exitRun.countDown();
166     }
167 
168     @Override
executor()169     protected Executor executor() {
170       return exceptionCatchingExecutor;
171     }
172   }
173 
testServiceThrowOnStartUp()174   public void testServiceThrowOnStartUp() throws Exception {
175     ThrowOnStartUpService service = new ThrowOnStartUpService();
176     assertFalse(service.startUpCalled);
177 
178     service.startAsync();
179     IllegalStateException expected =
180         assertThrows(IllegalStateException.class, () -> service.awaitRunning());
181     assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!");
182     executionThread.join();
183 
184     assertTrue(service.startUpCalled);
185     assertEquals(Service.State.FAILED, service.state());
186     assertThat(service.failureCause()).hasMessageThat().isEqualTo("kaboom!");
187   }
188 
189   private class ThrowOnStartUpService extends AbstractExecutionThreadService {
190     private boolean startUpCalled = false;
191 
192     @Override
startUp()193     protected void startUp() {
194       startUpCalled = true;
195       throw new UnsupportedOperationException("kaboom!");
196     }
197 
198     @Override
run()199     protected void run() {
200       throw new AssertionError("run() should not be called");
201     }
202 
203     @Override
executor()204     protected Executor executor() {
205       return exceptionCatchingExecutor;
206     }
207   }
208 
testServiceThrowOnRun()209   public void testServiceThrowOnRun() throws Exception {
210     ThrowOnRunService service = new ThrowOnRunService();
211 
212     service.startAsync();
213     IllegalStateException expected =
214         assertThrows(IllegalStateException.class, () -> service.awaitTerminated());
215     executionThread.join();
216     assertThat(expected).hasCauseThat().isEqualTo(service.failureCause());
217     assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!");
218     assertTrue(service.shutDownCalled);
219     assertEquals(Service.State.FAILED, service.state());
220   }
221 
testServiceThrowOnRunAndThenAgainOnShutDown()222   public void testServiceThrowOnRunAndThenAgainOnShutDown() throws Exception {
223     ThrowOnRunService service = new ThrowOnRunService();
224     service.throwOnShutDown = true;
225 
226     service.startAsync();
227     IllegalStateException expected =
228         assertThrows(IllegalStateException.class, () -> service.awaitTerminated());
229     executionThread.join();
230     assertThat(expected).hasCauseThat().isEqualTo(service.failureCause());
231     assertThat(expected).hasCauseThat().hasMessageThat().isEqualTo("kaboom!");
232     assertTrue(service.shutDownCalled);
233     assertEquals(Service.State.FAILED, service.state());
234   }
235 
236   private class ThrowOnRunService extends AbstractExecutionThreadService {
237     private boolean shutDownCalled = false;
238     private boolean throwOnShutDown = false;
239 
240     @Override
run()241     protected void run() {
242       throw new UnsupportedOperationException("kaboom!");
243     }
244 
245     @Override
shutDown()246     protected void shutDown() {
247       shutDownCalled = true;
248       if (throwOnShutDown) {
249         throw new UnsupportedOperationException("double kaboom!");
250       }
251     }
252 
253     @Override
executor()254     protected Executor executor() {
255       return exceptionCatchingExecutor;
256     }
257   }
258 
testServiceThrowOnShutDown()259   public void testServiceThrowOnShutDown() throws Exception {
260     ThrowOnShutDown service = new ThrowOnShutDown();
261 
262     service.startAsync().awaitRunning();
263     assertEquals(Service.State.RUNNING, service.state());
264 
265     service.stopAsync();
266     enterRun.countDown();
267     executionThread.join();
268 
269     assertEquals(Service.State.FAILED, service.state());
270     assertThat(service.failureCause()).hasMessageThat().isEqualTo("kaboom!");
271   }
272 
273   private class ThrowOnShutDown extends AbstractExecutionThreadService {
274     @Override
run()275     protected void run() {
276       try {
277         enterRun.await();
278       } catch (InterruptedException e) {
279         throw new RuntimeException(e);
280       }
281     }
282 
283     @Override
shutDown()284     protected void shutDown() {
285       throw new UnsupportedOperationException("kaboom!");
286     }
287 
288     @Override
executor()289     protected Executor executor() {
290       return exceptionCatchingExecutor;
291     }
292   }
293 
testServiceTimeoutOnStartUp()294   public void testServiceTimeoutOnStartUp() throws Exception {
295     TimeoutOnStartUp service = new TimeoutOnStartUp();
296 
297     TimeoutException e =
298         assertThrows(
299             TimeoutException.class,
300             () -> service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS));
301     assertThat(e.getMessage()).contains(Service.State.STARTING.toString());
302   }
303 
304   private class TimeoutOnStartUp extends AbstractExecutionThreadService {
305     @Override
executor()306     protected Executor executor() {
307       return new Executor() {
308         @Override
309         public void execute(Runnable command) {}
310       };
311     }
312 
313     @Override
run()314     protected void run() throws Exception {}
315   }
316 
testStopWhileStarting_runNotCalled()317   public void testStopWhileStarting_runNotCalled() throws Exception {
318     final CountDownLatch started = new CountDownLatch(1);
319     FakeService service =
320         new FakeService() {
321           @Override
322           protected void startUp() throws Exception {
323             super.startUp();
324             started.await();
325           }
326         };
327     service.startAsync();
328     service.stopAsync();
329     started.countDown();
330     service.awaitTerminated();
331     assertEquals(Service.State.TERMINATED, service.state());
332     assertEquals(1, service.startupCalled);
333     assertEquals(0, service.runCalled);
334     assertEquals(1, service.shutdownCalled);
335   }
336 
testStop_noStart()337   public void testStop_noStart() {
338     FakeService service = new FakeService();
339     service.stopAsync().awaitTerminated();
340     assertEquals(Service.State.TERMINATED, service.state());
341     assertEquals(0, service.startupCalled);
342     assertEquals(0, service.runCalled);
343     assertEquals(0, service.shutdownCalled);
344   }
345 
testDefaultService()346   public void testDefaultService() throws InterruptedException {
347     WaitOnRunService service = new WaitOnRunService();
348     service.startAsync().awaitRunning();
349     enterRun.await();
350     service.stopAsync().awaitTerminated();
351   }
352 
testTimeout()353   public void testTimeout() {
354     // Create a service whose executor will never run its commands
355     Service service =
356         new AbstractExecutionThreadService() {
357           @Override
358           protected void run() throws Exception {}
359 
360           @Override
361           protected ScheduledExecutorService executor() {
362             return TestingExecutors.noOpScheduledExecutor();
363           }
364 
365           @Override
366           protected String serviceName() {
367             return "Foo";
368           }
369         };
370     TimeoutException e =
371         assertThrows(
372             TimeoutException.class,
373             () -> service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS));
374     assertThat(e)
375         .hasMessageThat()
376         .isEqualTo("Timed out waiting for Foo [STARTING] to reach the RUNNING state.");
377   }
378 
379   private class FakeService extends AbstractExecutionThreadService implements TearDown {
380 
381     private final ExecutorService executor = Executors.newSingleThreadExecutor();
382 
FakeService()383     FakeService() {
384       tearDownStack.addTearDown(this);
385     }
386 
387     volatile int startupCalled = 0;
388     volatile int shutdownCalled = 0;
389     volatile int runCalled = 0;
390 
391     @Override
startUp()392     protected void startUp() throws Exception {
393       assertEquals(0, startupCalled);
394       assertEquals(0, runCalled);
395       assertEquals(0, shutdownCalled);
396       startupCalled++;
397     }
398 
399     @Override
run()400     protected void run() throws Exception {
401       assertEquals(1, startupCalled);
402       assertEquals(0, runCalled);
403       assertEquals(0, shutdownCalled);
404       runCalled++;
405     }
406 
407     @Override
shutDown()408     protected void shutDown() throws Exception {
409       assertEquals(1, startupCalled);
410       assertEquals(0, shutdownCalled);
411       assertEquals(Service.State.STOPPING, state());
412       shutdownCalled++;
413     }
414 
415     @Override
executor()416     protected Executor executor() {
417       return executor;
418     }
419 
420     @Override
tearDown()421     public void tearDown() throws Exception {
422       executor.shutdown();
423     }
424   }
425 }
426