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