Google-Guava Concurrent包里的Service框架浅析
概述
Guava包里的Service接口用于封装一个服务对象的运行状态、包括start和stop等方法。例如web服务器,RPC服务器、计时器等可以实现这个接口。对此类服务的状态管理并不轻松、需要对服务的开启/关闭进行妥善管理、特别是在多线程环境下尤为复杂。Guava包提供了一些基础类帮助你管理复杂的状态转换逻辑和同步细节。
使用一个服务
一个服务正常生命周期有:
- Service.State.NEW
- Service.State.STARTING
- Service.State.RUNNING
- Service.State.STOPPING
- Service.State.TERMINATED
服务一旦被停止就无法再重新启动了。如果服务在starting、running、stopping状态出现问题、会进入Service.State.FAILED.状态。调用 startAsync()方法可以异步开启一个服务,同时返回this对象形成方法调用链。注意:只有在当前服务的状态是NEW时才能调用startAsync()方法,因此最好在应用中有一个统一的地方初始化相关服务。停止一个服务也是类似的、使用异步方法stopAsync() 。但是不像startAsync(),多次调用这个方法是安全的。这是为了方便处理关闭服务时候的锁竞争问题。
Service也提供了一些方法用于等待服务状态转换的完成:
通过 addListener()方法异步添加监听器。此方法允许你添加一个 Service.Listener 、它会在每次服务状态转换的时候被调用。注意:最好在服务启动之前添加Listener(这时的状态是NEW)、否则之前已发生的状态转换事件是无法在新添加的Listener上被重新触发的。
同步使用awaitRunning()。这个方法不能被打断、不强制捕获异常、一旦服务启动就会返回。如果服务没有成功启动,会抛出IllegalStateException异常。同样的, awaitTerminated() 方法会等待服务达到终止状态(TERMINATED 或者 FAILED)。两个方法都有重载方法允许传入超时时间。
Service 接口本身实现起来会比较复杂、且容易碰到一些捉摸不透的问题。因此我们不推荐直接实现这个接口。而是请继承Guava包里已经封装好的基础抽象类。每个基础类支持一种特定的线程模型。
基础实现类
AbstractIdleService
AbstractIdleService 类简单实现了Service接口、其在running状态时不会执行任何动作–因此在running时也不需要启动线程–但需要处理开启/关闭动作。要实现一个此类的服务,只需继承AbstractIdleService类,然后自己实现startUp() 和shutDown()方法就可以了。
1 protected void startUp() {
2 servlets.add(new GcStatsServlet());
3 }
4 protected void shutDown() {}
如上面的例子、由于任何请求到GcStatsServlet时已经会有现成线程处理了,所以在服务运行时就不需要做什么额外动作了。
AbstractExecutionThreadService
AbstractExecutionThreadService 通过单线程处理启动、运行、和关闭等操作。你必须重载run()方法,同时需要能响应停止服务的请求。具体的实现可以在一个循环内做处理:
1 public void run() {
2 while (isRunning()) {
3 // perform a unit of work
4 }
5 }
另外,你还可以重载triggerShutdown()方法让run()方法结束返回。
重载startUp()和shutDown()方法是可选的,不影响服务本身状态的管理
01 protected void startUp() {
02 dispatcher.listenForConnections(port, queue);
03 }
04 protected void run() {
05 Connection connection;
06 while ((connection = queue.take() != POISON)) {
07 process(connection);
08 }
09 }
10 protected void triggerShutdown() {
11 dispatcher.stopListeningForConnections(queue);
12 queue.put(POISON);
13 }
start()内部会调用startUp()方法,创建一个线程、然后在线程内调用run()方法。stop()会调用 triggerShutdown()方法并且等待线程终止。
AbstractScheduledService
AbstractScheduledService类用于在运行时处理一些周期性的任务。子类可以实现 runOneIteration()方法定义一个周期执行的任务,以及相应的startUp()和shutDown()方法。为了能够描述执行周期,你需要实现scheduler()方法。通常情况下,你可以使用AbstractScheduledService.Scheduler类提供的两种调度器:newFixedRateSchedule(initialDelay, delay, TimeUnit) 和newFixedDelaySchedule(initialDelay, delay, TimeUnit),类似于JDK并发包中ScheduledExecutorService类提供的两种调度方式。如要自定义schedules则可以使用 CustomScheduler类来辅助实现;具体用法见javadoc。
AbstractService
如需要自定义的线程管理、可以通过扩展 AbstractService类来实现。一般情况下、使用上面的几个实现类就已经满足需求了,但如果在服务执行过程中有一些特定的线程处理需求、则建议继承AbstractService类。
继承AbstractService方法必须实现两个方法.
- doStart(): 首次调用startAsync()时会同时调用doStart(),doStart()内部需要处理所有的初始化工作、如果启动成功则调用notifyStarted()方法;启动失败则调用notifyFailed()
- doStop(): 首次调用stopAsync()会同时调用doStop(),doStop()要做的事情就是停止服务,如果停止成功则调用 notifyStopped()方法;停止失败则调用 notifyFailed()方法。
doStart和doStop方法的实现需要考虑下性能,尽可能的低延迟。如果初始化的开销较大,如读文件,打开网络连接,或者其他任何可能引起阻塞的操作,建议移到另外一个单独的线程去处理。
使用ServiceManager
除了对Service接口提供基础的实现类,Guava还提供了 ServiceManager类使得涉及到多个Service集合的操作更加容易。通过实例化ServiceManager类来创建一个Service集合,你可以通过以下方法来管理它们:
- startAsync() : 将启动所有被管理的服务。如果当前服务的状态都是NEW的话、那么你只能调用该方法一次、这跟 Service#startAsync()是一样的。
- stopAsync() :将停止所有被管理的服务。
- addListener :会添加一个ServiceManager.Listener,在服务状态转换中会调用该Listener
- awaitHealthy() :会等待所有的服务达到Running状态
- awaitStopped():会等待所有服务达到终止状态
检测类的方法有:
- isHealthy() :如果所有的服务处于Running状态、会返回True
- servicesByState():以状态为索引返回当前所有服务的快照
- startupTimes() :返回一个Map对象,记录被管理的服务启动的耗时、以毫秒为单位,同时Map默认按启动时间排序。
我们建议整个服务的生命周期都能通过ServiceManager来管理,不过即使状态转换是通过其他机制触发的、也不影响ServiceManager方法的正确执行。例如:当一个服务不是通过startAsync()、而是其他机制启动时,listeners 仍然可以被正常调用、awaitHealthy()也能够正常工作。ServiceManager 唯一强制的要求是当其被创建时所有的服务必须处于New状态。
附:TestCase、也可以作为练习Demo
ServiceTest
01 </pre>
02 /*
03 * Copyright (C) 2013 The Guava Authors
04 *
05 * Licensed under the Apache License, Version 2.0 (the "License");
06 * you may not use this file except in compliance with the License.
07 * You may obtain a copy of the License at
08 *
09 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package com.google.common.util.concurrent;
19
20 import static com.google.common.util.concurrent.Service.State.FAILED;
21 import static com.google.common.util.concurrent.Service.State.NEW;
22 import static com.google.common.util.concurrent.Service.State.RUNNING;
23 import static com.google.common.util.concurrent.Service.State.STARTING;
24 import static com.google.common.util.concurrent.Service.State.STOPPING;
25 import static com.google.common.util.concurrent.Service.State.TERMINATED;
26
27 import junit.framework.TestCase;
28
29 /**
30 * Unit tests for {@link Service}
31 */
32 public class ServiceTest extends TestCase {
33
34 /** Assert on the comparison ordering of the State enum since we guarantee it. */
35 public void testStateOrdering() {
36 // List every valid (direct) state transition.
37 assertLessThan(NEW, STARTING);
38 assertLessThan(NEW, TERMINATED);
39
40 assertLessThan(STARTING, RUNNING);
41 assertLessThan(STARTING, STOPPING);
42 assertLessThan(STARTING, FAILED);
43
44 assertLessThan(RUNNING, STOPPING);
45 assertLessThan(RUNNING, FAILED);
46
47 assertLessThan(STOPPING, FAILED);
48 assertLessThan(STOPPING, TERMINATED);
49 }
50
51 private static <T extends Comparable<? super T>> void assertLessThan(T a, T b) {
52 if (a.compareTo(b) >= 0) {
53 fail(String.format("Expected %s to be less than %s", a, b));
54 }
55 }
56 }
57 <pre>
AbstractIdleServiceTest 001 /*
002 * Copyright (C) 2009 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package com.google.common.util.concurrent;
018
019 import static org.truth0.Truth.ASSERT;
020
021 import com.google.common.collect.Lists;
022
023 import junit.framework.TestCase;
024
025 import java.util.List;
026 import java.util.concurrent.Executor;
027 import java.util.concurrent.TimeUnit;
028 import java.util.concurrent.TimeoutException;
029
030 /**
031 * Tests for {@link AbstractIdleService}.
032 *
033 * @author Chris Nokleberg
034 * @author Ben Yu
035 */
036 public class AbstractIdleServiceTest extends TestCase {
037
038 // Functional tests using real thread. We only verify publicly visible state.
039 // Interaction assertions are done by the single-threaded unit tests.
040
041 public static class FunctionalTest extends TestCase {
042
043 private static class DefaultService extends AbstractIdleService {
044 @Override protected void startUp() throws Exception {}
045 @Override protected void shutDown() throws Exception {}
046 }
047
048 public void testServiceStartStop() throws Exception {
049 AbstractIdleService service = new DefaultService();
050 service.startAsync().awaitRunning();
051 assertEquals(Service.State.RUNNING, service.state());
052 service.stopAsync().awaitTerminated();
053 assertEquals(Service.State.TERMINATED, service.state());
054 }
055
056 public void testStart_failed() throws Exception {
057 final Exception exception = new Exception("deliberate");
058 AbstractIdleService service = new DefaultService() {
059 @Override protected void startUp() throws Exception {
060 throw exception;
061 }
062 };
063 try {
064 service.startAsync().awaitRunning();
065 fail();
066 } catch (RuntimeException e) {
067 assertSame(exception, e.getCause());
068 }
069 assertEquals(Service.State.FAILED, service.state());
070 }
071
072 public void testStop_failed() throws Exception {
073 final Exception exception = new Exception("deliberate");
074 AbstractIdleService service = new DefaultService() {
075 @Override protected void shutDown() throws Exception {
076 throw exception;
077 }
078 };
079 service.startAsync().awaitRunning();
080 try {
081 service.stopAsync().awaitTerminated();
082 fail();
083 } catch (RuntimeException e) {
084 assertSame(exception, e.getCause());
085 }
086 assertEquals(Service.State.FAILED, service.state());
087 }
088 }
089
090 public void testStart() {
091 TestService service = new TestService();
092 assertEquals(0, service.startUpCalled);
093 service.startAsync().awaitRunning();
094 assertEquals(1, service.startUpCalled);
095 assertEquals(Service.State.RUNNING, service.state());
096 ASSERT.that(service.transitionStates).has().exactly(Service.State.STARTING).inOrder();
097 }
098
099 public void testStart_failed() {
100 final Exception exception = new Exception("deliberate");
101 TestService service = new TestService() {
102 @Override protected void startUp() throws Exception {
103 super.startUp();
104 throw exception;
105 }
106 };
107 assertEquals(0, service.startUpCalled);
108 try {
109 service.startAsync().awaitRunning();
110 fail();
111 } catch (RuntimeException e) {
112 assertSame(exception, e.getCause());
113 }
114 assertEquals(1, service.startUpCalled);
115 assertEquals(Service.State.FAILED, service.state());
116 ASSERT.that(service.transitionStates).has().exactly(Service.State.STARTING).inOrder();
117 }
118
119 public void testStop_withoutStart() {
120 TestService service = new TestService();
121 service.stopAsync().awaitTerminated();
122 assertEquals(0, service.startUpCalled);
123 assertEquals(0, service.shutDownCalled);
124 assertEquals(Service.State.TERMINATED, service.state());
125 ASSERT.that(service.transitionStates).isEmpty();
126 }
127
128 public void testStop_afterStart() {
129 TestService service = new TestService();
130 service.startAsync().awaitRunning();
131 assertEquals(1, service.startUpCalled);
132 assertEquals(0, service.shutDownCalled);
133 service.stopAsync().awaitTerminated();
134 assertEquals(1, service.startUpCalled);
135 assertEquals(1, service.shutDownCalled);
136 assertEquals(Service.State.TERMINATED, service.state());
137 ASSERT.that(service.transitionStates)
138 .has().exactly(Service.State.STARTING, Service.State.STOPPING).inOrder();
139 }
140
141 public void testStop_failed() {
142 final Exception exception = new Exception("deliberate");
143 TestService service = new TestService() {
144 @Override protected void shutDown() throws Exception {
145 super.shutDown();
146 throw exception;
147 }
148 };
149 service.startAsync().awaitRunning();
150 assertEquals(1, service.startUpCalled);
151 assertEquals(0, service.shutDownCalled);
152 try {
153 service.stopAsync().awaitTerminated();
154 fail();
155 } catch (RuntimeException e) {
156 assertSame(exception, e.getCause());
157 }
158 assertEquals(1, service.startUpCalled);
159 assertEquals(1, service.shutDownCalled);
160 assertEquals(Service.State.FAILED, service.state());
161 ASSERT.that(service.transitionStates)
162 .has().exactly(Service.State.STARTING, Service.State.STOPPING).inOrder();
163 }
164
165 public void testServiceToString() {
166 AbstractIdleService service = new TestService();
167 assertEquals("TestService [NEW]", service.toString());
168 service.startAsync().awaitRunning();
169 assertEquals("TestService [RUNNING]", service.toString());
170 service.stopAsync().awaitTerminated();
171 assertEquals("TestService [TERMINATED]", service.toString());
172 }
173
174 public void testTimeout() throws Exception {
175 // Create a service whose executor will never run its commands
176 Service service = new TestService() {
177 @Override protected Executor executor() {
178 return new Executor() {
179 @Override public void execute(Runnable command) {}
180 };
181 }
182 };
183 try {
184 service.startAsync().awaitRunning(1, TimeUnit.MILLISECONDS);
185 fail("Expected timeout");
186 } catch (TimeoutException e) {
187 ASSERT.that(e.getMessage()).contains(Service.State.STARTING.toString());
188 }
189 }
190
191 private static class TestService extends AbstractIdleService {
192 int startUpCalled = 0;
193 int shutDownCalled = 0;
194 final List<State> transitionStates = Lists.newArrayList();
195
196 @Override protected void startUp() throws Exception {
197 assertEquals(0, startUpCalled);
198 assertEquals(0, shutDownCalled);
199 startUpCalled++;
200 assertEquals(State.STARTING, state());
201 }
202
203 @Override protected void shutDown() throws Exception {
204 assertEquals(1, startUpCalled);
205 assertEquals(0, shutDownCalled);
206 shutDownCalled++;
207 assertEquals(State.STOPPING, state());
208 }
209
210 @Override protected Executor executor() {
211 transitionStates.add(state());
212 return MoreExecutors.sameThreadExecutor();
213 }
214 }
215 }
216
217 <pre>
AbstractScheduledServiceTest 001 </pre>
002 /*
003 * Copyright (C) 2011 The Guava Authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package com.google.common.util.concurrent;
019
020 import com.google.common.util.concurrent.AbstractScheduledService.Scheduler;
021 import com.google.common.util.concurrent.Service.State;
022
023 import junit.framework.TestCase;
024
025 import java.util.concurrent.CountDownLatch;
026 import java.util.concurrent.CyclicBarrier;
027 import java.util.concurrent.ExecutionException;
028 import java.util.concurrent.Executors;
029 import java.util.concurrent.Future;
030 import java.util.concurrent.ScheduledExecutorService;
031 import java.util.concurrent.ScheduledFuture;
032 import java.util.concurrent.ScheduledThreadPoolExecutor;
033 import java.util.concurrent.TimeUnit;
034 import java.util.concurrent.atomic.AtomicBoolean;
035 import java.util.concurrent.atomic.AtomicInteger;
036
037 /**
038 * Unit test for {@link AbstractScheduledService}.
039 *
040 * @author Luke Sandberg
041 */
042
043 public class AbstractScheduledServiceTest extends TestCase {
044
045 volatile Scheduler configuration = Scheduler.newFixedDelaySchedule(0, 10, TimeUnit.MILLISECONDS);
046 volatile ScheduledFuture<?> future = null;
047
048 volatile boolean atFixedRateCalled = false;
049 volatile boolean withFixedDelayCalled = false;
050 volatile boolean scheduleCalled = false;
051
052 final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(10) {
053 @Override
054 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
055 long delay, TimeUnit unit) {
056 return future = super.scheduleWithFixedDelay(command, initialDelay, delay, unit);
057 }
058 };
059
060 public void testServiceStartStop() throws Exception {
061 NullService service = new NullService();
062 service.startAsync().awaitRunning();
063 assertFalse(future.isDone());
064 service.stopAsync().awaitTerminated();
065 assertTrue(future.isCancelled());
066 }
067
068 private class NullService extends AbstractScheduledService {
069 @Override protected void runOneIteration() throws Exception {}
070 @Override protected Scheduler scheduler() { return configuration; }
071 @Override protected ScheduledExecutorService executor() { return executor; }
072 }
073
074 public void testFailOnExceptionFromRun() throws Exception {
075 TestService service = new TestService();
076 service.runException = new Exception();
077 service.startAsync().awaitRunning();
078 service.runFirstBarrier.await();
079 service.runSecondBarrier.await();
080 try {
081 future.get();
082 fail();
083 } catch (ExecutionException e) {
084 // An execution exception holds a runtime exception (from throwables.propogate) that holds our
085 // original exception.
086 assertEquals(service.runException, e.getCause().getCause());
087 }
088 assertEquals(service.state(), Service.State.FAILED);
089 }
090
091 public void testFailOnExceptionFromStartUp() {
092 TestService service = new TestService();
093 service.startUpException = new Exception();
094 try {
095 service.startAsync().awaitRunning();
096 fail();
097 } catch (IllegalStateException e) {
098 assertEquals(service.startUpException, e.getCause());
099 }
100 assertEquals(0, service.numberOfTimesRunCalled.get());
101 assertEquals(Service.State.FAILED, service.state());
102 }
103
104 public void testFailOnExceptionFromShutDown() throws Exception {
105 TestService service = new TestService();
106 service.shutDownException = new Exception();
107 service.startAsync().awaitRunning();
108 service.runFirstBarrier.await();
109 service.stopAsync();
110 service.runSecondBarrier.await();
111 try {
112 service.awaitTerminated();
113 fail();
114 } catch (IllegalStateException e) {
115 assertEquals(service.shutDownException, e.getCause());
116 }
117 assertEquals(Service.State.FAILED, service.state());
118 }
119
120 public void testRunOneIterationCalledMultipleTimes() throws Exception {
121 TestService service = new TestService();
122 service.startAsync().awaitRunning();
123 for (int i = 1; i < 10; i++) {
124 service.runFirstBarrier.await();
125 assertEquals(i, service.numberOfTimesRunCalled.get());
126 service.runSecondBarrier.await();
127 }
128 service.runFirstBarrier.await();
129 service.stopAsync();
130 service.runSecondBarrier.await();
131 service.stopAsync().awaitTerminated();
132 }
133
134 public void testExecutorOnlyCalledOnce() throws Exception {
135 TestService service = new TestService();
136 service.startAsync().awaitRunning();
137 // It should be called once during startup.
138 assertEquals(1, service.numberOfTimesExecutorCalled.get());
139 for (int i = 1; i < 10; i++) {
140 service.runFirstBarrier.await();
141 assertEquals(i, service.numberOfTimesRunCalled.get());
142 service.runSecondBarrier.await();
143 }
144 service.runFirstBarrier.await();
145 service.stopAsync();
146 service.runSecondBarrier.await();
147 service.stopAsync().awaitTerminated();
148 // Only called once overall.
149 assertEquals(1, service.numberOfTimesExecutorCalled.get());
150 }
151
152 public void testDefaultExecutorIsShutdownWhenServiceIsStopped() throws Exception {
153 final CountDownLatch terminationLatch = new CountDownLatch(1);
154 AbstractScheduledService service = new AbstractScheduledService() {
155 volatile ScheduledExecutorService executorService;
156 @Override protected void runOneIteration() throws Exception {}
157
158 @Override protected ScheduledExecutorService executor() {
159 if (executorService == null) {
160 executorService = super.executor();
161 // Add a listener that will be executed after the listener that shuts down the executor.
162 addListener(new Listener() {
163 @Override public void terminated(State from) {
164 terminationLatch.countDown();
165 }
166 }, MoreExecutors.sameThreadExecutor());
167 }
168 return executorService;
169 }
170
171 @Override protected Scheduler scheduler() {
172 return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.MILLISECONDS);
173 }};
174
175 service.startAsync();
176 assertFalse(service.executor().isShutdown());
177 service.awaitRunning();
178 service.stopAsync();
179 terminationLatch.await();
180 assertTrue(service.executor().isShutdown());
181 assertTrue(service.executor().awaitTermination(100, TimeUnit.MILLISECONDS));
182 }
183
184 public void testDefaultExecutorIsShutdownWhenServiceFails() throws Exception {
185 final CountDownLatch failureLatch = new CountDownLatch(1);
186 AbstractScheduledService service = new AbstractScheduledService() {
187 volatile ScheduledExecutorService executorService;
188 @Override protected void runOneIteration() throws Exception {}
189
190 @Override protected void startUp() throws Exception {
191 throw new Exception("Failed");
192 }
193
194 @Override protected ScheduledExecutorService executor() {
195 if (executorService == null) {
196 executorService = super.executor();
197 // Add a listener that will be executed after the listener that shuts down the executor.
198 addListener(new Listener() {
199 @Override public void failed(State from, Throwable failure) {
200 failureLatch.countDown();
201 }
202 }, MoreExecutors.sameThreadExecutor());
203 }
204 return executorService;
205 }
206
207 @Override protected Scheduler scheduler() {
208 return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.MILLISECONDS);
209 }};
210
211 try {
212 service.startAsync().awaitRunning();
213 fail("Expected service to fail during startup");
214 } catch (IllegalStateException expected) {}
215 failureLatch.await();
216 assertTrue(service.executor().isShutdown());
217 assertTrue(service.executor().awaitTermination(100, TimeUnit.MILLISECONDS));
218 }
219
220 public void testSchedulerOnlyCalledOnce() throws Exception {
221 TestService service = new TestService();
222 service.startAsync().awaitRunning();
223 // It should be called once during startup.
224 assertEquals(1, service.numberOfTimesSchedulerCalled.get());
225 for (int i = 1; i < 10; i++) {
226 service.runFirstBarrier.await();
227 assertEquals(i, service.numberOfTimesRunCalled.get());
228 service.runSecondBarrier.await();
229 }
230 service.runFirstBarrier.await();
231 service.stopAsync();
232 service.runSecondBarrier.await();
233 service.awaitTerminated();
234 // Only called once overall.
235 assertEquals(1, service.numberOfTimesSchedulerCalled.get());
236 }
237
238 private class TestService extends AbstractScheduledService {
239 CyclicBarrier runFirstBarrier = new CyclicBarrier(2);
240 CyclicBarrier runSecondBarrier = new CyclicBarrier(2);
241
242 volatile boolean startUpCalled = false;
243 volatile boolean shutDownCalled = false;
244 AtomicInteger numberOfTimesRunCalled = new AtomicInteger(0);
245 AtomicInteger numberOfTimesExecutorCalled = new AtomicInteger(0);
246 AtomicInteger numberOfTimesSchedulerCalled = new AtomicInteger(0);
247 volatile Exception runException = null;
248 volatile Exception startUpException = null;
249 volatile Exception shutDownException = null;
250
251 @Override
252 protected void runOneIteration() throws Exception {
253 assertTrue(startUpCalled);
254 assertFalse(shutDownCalled);
255 numberOfTimesRunCalled.incrementAndGet();
256 assertEquals(State.RUNNING, state());
257 runFirstBarrier.await();
258 runSecondBarrier.await();
259 if (runException != null) {
260 throw runException;
261 }
262 }
263
264 @Override
265 protected void startUp() throws Exception {
266 assertFalse(startUpCalled);
267 assertFalse(shutDownCalled);
268 startUpCalled = true;
269 assertEquals(State.STARTING, state());
270 if (startUpException != null) {
271 throw startUpException;
272 }
273 }
274
275 @Override
276 protected void shutDown() throws Exception {
277 assertTrue(startUpCalled);
278 assertFalse(shutDownCalled);
279 shutDownCalled = true;
280 if (shutDownException != null) {
281 throw shutDownException;
282 }
283 }
284
285 @Override
286 protected ScheduledExecutorService executor() {
287 numberOfTimesExecutorCalled.incrementAndGet();
288 return executor;
289 }
290
291 @Override
292 protected Scheduler scheduler() {
293 numberOfTimesSchedulerCalled.incrementAndGet();
294 return configuration;
295 }
296 }
297
298 public static class SchedulerTest extends TestCase {
299 // These constants are arbitrary and just used to make sure that the correct method is called
300 // with the correct parameters.
301 private static final int initialDelay = 10;
302 private static final int delay = 20;
303 private static final TimeUnit unit = TimeUnit.MILLISECONDS;
304
305 // Unique runnable object used for comparison.
306 final Runnable testRunnable = new Runnable() {@Override public void run() {}};
307 boolean called = false;
308
309 private void assertSingleCallWithCorrectParameters(Runnable command, long initialDelay,
310 long delay, TimeUnit unit) {
311 assertFalse(called); // only called once.
312 called = true;
313 assertEquals(SchedulerTest.initialDelay, initialDelay);
314 assertEquals(SchedulerTest.delay, delay);
315 assertEquals(SchedulerTest.unit, unit);
316 assertEquals(testRunnable, command);
317 }
318
319 public void testFixedRateSchedule() {
320 Scheduler schedule = Scheduler.newFixedRateSchedule(initialDelay, delay, unit);
321 schedule.schedule(null, new ScheduledThreadPoolExecutor(1) {
322 @Override
323 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
324 long period, TimeUnit unit) {
325 assertSingleCallWithCorrectParameters(command, initialDelay, delay, unit);
326 return null;
327 }
328 }, testRunnable);
329 assertTrue(called);
330 }
331
332 public void testFixedDelaySchedule() {
333 Scheduler schedule = Scheduler.newFixedDelaySchedule(initialDelay, delay, unit);
334 schedule.schedule(null, new ScheduledThreadPoolExecutor(10) {
335 @Override
336 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
337 long delay, TimeUnit unit) {
338 assertSingleCallWithCorrectParameters(command, initialDelay, delay, unit);
339 return null;
340 }
341 }, testRunnable);
342 assertTrue(called);
343 }
344
345 private class TestCustomScheduler extends AbstractScheduledService.CustomScheduler {
346 public AtomicInteger scheduleCounter = new AtomicInteger(0);
347 @Override
348 protected Schedule getNextSchedule() throws Exception {
349 scheduleCounter.incrementAndGet();
350 return new Schedule(0, TimeUnit.SECONDS);
351 }
352 }
353
354 public void testCustomSchedule_startStop() throws Exception {
355 final CyclicBarrier firstBarrier = new CyclicBarrier(2);
356 final CyclicBarrier secondBarrier = new CyclicBarrier(2);
357 final AtomicBoolean shouldWait = new AtomicBoolean(true);
358 Runnable task = new Runnable() {
359 @Override public void run() {
360 try {
361 if (shouldWait.get()) {
362 firstBarrier.await();
363 secondBarrier.await();
364 }
365 } catch (Exception e) {
366 throw new RuntimeException(e);
367 }
368 }
369 };
370 TestCustomScheduler scheduler = new TestCustomScheduler();
371 Future<?> future = scheduler.schedule(null, Executors.newScheduledThreadPool(10), task);
372 firstBarrier.await();
373 assertEquals(1, scheduler.scheduleCounter.get());
374 secondBarrier.await();
375 firstBarrier.await();
376 assertEquals(2, scheduler.scheduleCounter.get());
377 shouldWait.set(false);
378 secondBarrier.await();
379 future.cancel(false);
380 }
381
382 public void testCustomSchedulerServiceStop() throws Exception {
383 TestAbstractScheduledCustomService service = new TestAbstractScheduledCustomService();
384 service.startAsync().awaitRunning();
385 service.firstBarrier.await();
386 assertEquals(1, service.numIterations.get());
387 service.stopAsync();
388 service.secondBarrier.await();
389 service.awaitTerminated();
390 // Sleep for a while just to ensure that our task wasn't called again.
391 Thread.sleep(unit.toMillis(3 * delay));
392 assertEquals(1, service.numIterations.get());
393 }
394
395 public void testBig() throws Exception {
396 TestAbstractScheduledCustomService service = new TestAbstractScheduledCustomService() {
397 @Override protected Scheduler scheduler() {
398 return new AbstractScheduledService.CustomScheduler() {
399 @Override
400 protected Schedule getNextSchedule() throws Exception {
401 // Explicitly yield to increase the probability of a pathological scheduling.
402 Thread.yield();
403 return new Schedule(0, TimeUnit.SECONDS);
404 }
405 };
406 }
407 };
408 service.useBarriers = false;
409 service.startAsync().awaitRunning();
410 Thread.sleep(50);
411 service.useBarriers = true;
412 service.firstBarrier.await();
413 int numIterations = service.numIterations.get();
414 service.stopAsync();
415 service.secondBarrier.await();
416 service.awaitTerminated();
417 assertEquals(numIterations, service.numIterations.get());
418 }
419
420 private static class TestAbstractScheduledCustomService extends AbstractScheduledService {
421 final AtomicInteger numIterations = new AtomicInteger(0);
422 volatile boolean useBarriers = true;
423 final CyclicBarrier firstBarrier = new CyclicBarrier(2);
424 final CyclicBarrier secondBarrier = new CyclicBarrier(2);
425
426 @Override protected void runOneIteration() throws Exception {
427 numIterations.incrementAndGet();
428 if (useBarriers) {
429 firstBarrier.await();
430 secondBarrier.await();
431 }
432 }
433
434 @Override protected ScheduledExecutorService executor() {
435 // use a bunch of threads so that weird overlapping schedules are more likely to happen.
436 return Executors.newScheduledThreadPool(10);
437 }
438
439 @Override protected void startUp() throws Exception {}
440
441 @Override protected void shutDown() throws Exception {}
442
443 @Override protected Scheduler scheduler() {
444 return new CustomScheduler() {
445 @Override
446 protected Schedule getNextSchedule() throws Exception {
447 return new Schedule(delay, unit);
448 }};
449 }
450 }
451
452 public void testCustomSchedulerFailure() throws Exception {
453 TestFailingCustomScheduledService service = new TestFailingCustomScheduledService();
454 service.startAsync().awaitRunning();
455 for (int i = 1; i < 4; i++) {
456 service.firstBarrier.await();
457 assertEquals(i, service.numIterations.get());
458 service.secondBarrier.await();
459 }
460 Thread.sleep(1000);
461 try {
462 service.stopAsync().awaitTerminated(100, TimeUnit.SECONDS);
463 fail();
464 } catch (IllegalStateException e) {
465 assertEquals(State.FAILED, service.state());
466 }
467 }
468
469 private static class TestFailingCustomScheduledService extends AbstractScheduledService {
470 final AtomicInteger numIterations = new AtomicInteger(0);
471 final CyclicBarrier firstBarrier = new CyclicBarrier(2);
472 final CyclicBarrier secondBarrier = new CyclicBarrier(2);
473
474 @Override protected void runOneIteration() throws Exception {
475 numIterations.incrementAndGet();
476 firstBarrier.await();
477 secondBarrier.await();
478 }
479
480 @Override protected ScheduledExecutorService executor() {
481 // use a bunch of threads so that weird overlapping schedules are more likely to happen.
482 return Executors.newScheduledThreadPool(10);
483 }
484
485 @Override protected Scheduler scheduler() {
486 return new CustomScheduler() {
487 @Override
488 protected Schedule getNextSchedule() throws Exception {
489 if (numIterations.get() > 2) {
490 throw new IllegalStateException("Failed");
491 }
492 return new Schedule(delay, unit);
493 }};
494 }
495 }
496 }
497 }
498 <pre>
AbstractServiceTest 001 </pre>
002 /*
003 * Copyright (C) 2009 The Guava Authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package com.google.common.util.concurrent;
019
020 import static java.lang.Thread.currentThread;
021 import static java.util.concurrent.TimeUnit.SECONDS;
022
023 import com.google.common.collect.ImmutableList;
024 import com.google.common.collect.Iterables;
025 import com.google.common.collect.Lists;
026 import com.google.common.util.concurrent.Service.Listener;
027 import com.google.common.util.concurrent.Service.State;
028
029 import junit.framework.TestCase;
030
031 import java.lang.Thread.UncaughtExceptionHandler;
032 import java.util.List;
033 import java.util.concurrent.CountDownLatch;
034 import java.util.concurrent.TimeUnit;
035 import java.util.concurrent.atomic.AtomicInteger;
036 import java.util.concurrent.atomic.AtomicReference;
037
038 import javax.annotation.concurrent.GuardedBy;
039
040 /**
041 * Unit test for {@link AbstractService}.
042 *
043 * @author Jesse Wilson
044 */
045 public class AbstractServiceTest extends TestCase {
046
047 private Thread executionThread;
048 private Throwable thrownByExecutionThread;
049
050 public void testNoOpServiceStartStop() throws Exception {
051 NoOpService service = new NoOpService();
052 RecordingListener listener = RecordingListener.record(service);
053
054 assertEquals(State.NEW, service.state());
055 assertFalse(service.isRunning());
056 assertFalse(service.running);
057
058 service.startAsync();
059 assertEquals(State.RUNNING, service.state());
060 assertTrue(service.isRunning());
061 assertTrue(service.running);
062
063 service.stopAsync();
064 assertEquals(State.TERMINATED, service.state());
065 assertFalse(service.isRunning());
066 assertFalse(service.running);
067 assertEquals(
068 ImmutableList.of(
069 State.STARTING,
070 State.RUNNING,
071 State.STOPPING,
072 State.TERMINATED),
073 listener.getStateHistory());
074 }
075
076 public void testNoOpServiceStartAndWaitStopAndWait() throws Exception {
077 NoOpService service = new NoOpService();
078
079 service.startAsync().awaitRunning();
080 assertEquals(State.RUNNING, service.state());
081
082 service.stopAsync().awaitTerminated();
083 assertEquals(State.TERMINATED, service.state());
084 }
085
086 public void testNoOpServiceStartAsyncAndAwaitStopAsyncAndAwait() throws Exception {
087 NoOpService service = new NoOpService();
088
089 service.startAsync().awaitRunning();
090 assertEquals(State.RUNNING, service.state());
091
092 service.stopAsync().awaitTerminated();
093 assertEquals(State.TERMINATED, service.state());
094 }
095
096 public void testNoOpServiceStopIdempotence() throws Exception {
097 NoOpService service = new NoOpService();
098 RecordingListener listener = RecordingListener.record(service);
099 service.startAsync().awaitRunning();
100 assertEquals(State.RUNNING, service.state());
101
102 service.stopAsync();
103 service.stopAsync();
104 assertEquals(State.TERMINATED, service.state());
105 assertEquals(
106 ImmutableList.of(
107 State.STARTING,
108 State.RUNNING,
109 State.STOPPING,
110 State.TERMINATED),
111 listener.getStateHistory());
112 }
113
114 public void testNoOpServiceStopIdempotenceAfterWait() throws Exception {
115 NoOpService service = new NoOpService();
116
117 service.startAsync().awaitRunning();
118
119 service.stopAsync().awaitTerminated();
120 service.stopAsync();
121 assertEquals(State.TERMINATED, service.state());
122 }
123
124 public void testNoOpServiceStopIdempotenceDoubleWait() throws Exception {
125 NoOpService service = new NoOpService();
126
127 service.startAsync().awaitRunning();
128 assertEquals(State.RUNNING, service.state());
129
130 service.stopAsync().awaitTerminated();
131 service.stopAsync().awaitTerminated();
132 assertEquals(State.TERMINATED, service.state());
133 }
134
135 public void testNoOpServiceStartStopAndWaitUninterruptible()
136 throws Exception {
137 NoOpService service = new NoOpService();
138
139 currentThread().interrupt();
140 try {
141 service.startAsync().awaitRunning();
142 assertEquals(State.RUNNING, service.state());
143
144 service.stopAsync().awaitTerminated();
145 assertEquals(State.TERMINATED, service.state());
146
147 assertTrue(currentThread().isInterrupted());
148 } finally {
149 Thread.interrupted(); // clear interrupt for future tests
150 }
151 }
152
153 private static class NoOpService extends AbstractService {
154 boolean running = false;
155
156 @Override protected void doStart() {
157 assertFalse(running);
158 running = true;
159 notifyStarted();
160 }
161
162 @Override protected void doStop() {
163 assertTrue(running);
164 running = false;
165 notifyStopped();
166 }
167 }
168
169 public void testManualServiceStartStop() throws Exception {
170 ManualSwitchedService service = new ManualSwitchedService();
171 RecordingListener listener = RecordingListener.record(service);
172
173 service.startAsync();
174 assertEquals(State.STARTING, service.state());
175 assertFalse(service.isRunning());
176 assertTrue(service.doStartCalled);
177
178 service.notifyStarted(); // usually this would be invoked by another thread
179 assertEquals(State.RUNNING, service.state());
180 assertTrue(service.isRunning());
181
182 service.stopAsync();
183 assertEquals(State.STOPPING, service.state());
184 assertFalse(service.isRunning());
185 assertTrue(service.doStopCalled);
186
187 service.notifyStopped(); // usually this would be invoked by another thread
188 assertEquals(State.TERMINATED, service.state());
189 assertFalse(service.isRunning());
190 assertEquals(
191 ImmutableList.of(
192 State.STARTING,
193 State.RUNNING,
194 State.STOPPING,
195 State.TERMINATED),
196 listener.getStateHistory());
197
198 }
199
200 public void testManualServiceNotifyStoppedWhileRunning() throws Exception {
201 ManualSwitchedService service = new ManualSwitchedService();
202 RecordingListener listener = RecordingListener.record(service);
203
204 service.startAsync();
205 service.notifyStarted();
206 service.notifyStopped();
207 assertEquals(State.TERMINATED, service.state());
208 assertFalse(service.isRunning());
209 assertFalse(service.doStopCalled);
210
211 assertEquals(
212 ImmutableList.of(
213 State.STARTING,
214 State.RUNNING,
215 State.TERMINATED),
216 listener.getStateHistory());
217 }
218
219 public void testManualServiceStopWhileStarting() throws Exception {
220 ManualSwitchedService service = new ManualSwitchedService();
221 RecordingListener listener = RecordingListener.record(service);
222
223 service.startAsync();
224 assertEquals(State.STARTING, service.state());
225 assertFalse(service.isRunning());
226 assertTrue(service.doStartCalled);
227
228 service.stopAsync();
229 assertEquals(State.STOPPING, service.state());
230 assertFalse(service.isRunning());
231 assertFalse(service.doStopCalled);
232
233 service.notifyStarted();
234 assertEquals(State.STOPPING, service.state());
235 assertFalse(service.isRunning());
236 assertTrue(service.doStopCalled);
237
238 service.notifyStopped();
239 assertEquals(State.TERMINATED, service.state());
240 assertFalse(service.isRunning());
241 assertEquals(
242 ImmutableList.of(
243 State.STARTING,
244 State.STOPPING,
245 State.TERMINATED),
246 listener.getStateHistory());
247 }
248
249 /**
250 * This tests for a bug where if {@link Service#stopAsync()} was called while the service was
251 * {@link State#STARTING} more than once, the {@link Listener#stopping(State)} callback would get
252 * called multiple times.
253 */
254 public void testManualServiceStopMultipleTimesWhileStarting() throws Exception {
255 ManualSwitchedService service = new ManualSwitchedService();
256 final AtomicInteger stopppingCount = new AtomicInteger();
257 service.addListener(new Listener() {
258 @Override public void stopping(State from) {
259 stopppingCount.incrementAndGet();
260 }
261 }, MoreExecutors.sameThreadExecutor());
262
263 service.startAsync();
264 service.stopAsync();
265 assertEquals(1, stopppingCount.get());
266 service.stopAsync();
267 assertEquals(1, stopppingCount.get());
268 }
269
270 public void testManualServiceStopWhileNew() throws Exception {
271 ManualSwitchedService service = new ManualSwitchedService();
272 RecordingListener listener = RecordingListener.record(service);
273
274 service.stopAsync();
275 assertEquals(State.TERMINATED, service.state());
276 assertFalse(service.isRunning());
277 assertFalse(service.doStartCalled);
278 assertFalse(service.doStopCalled);
279 assertEquals(ImmutableList.of(State.TERMINATED), listener.getStateHistory());
280 }
281
282 public void testManualServiceFailWhileStarting() throws Exception {
283 ManualSwitchedService service = new ManualSwitchedService();
284 RecordingListener listener = RecordingListener.record(service);
285 service.startAsync();
286 service.notifyFailed(EXCEPTION);
287 assertEquals(ImmutableList.of(State.STARTING, State.FAILED), listener.getStateHistory());
288 }
289
290 public void testManualServiceFailWhileRunning() throws Exception {
291 ManualSwitchedService service = new ManualSwitchedService();
292 RecordingListener listener = RecordingListener.record(service);
293 service.startAsync();
294 service.notifyStarted();
295 service.notifyFailed(EXCEPTION);
296 assertEquals(ImmutableList.of(State.STARTING, State.RUNNING, State.FAILED),
297 listener.getStateHistory());
298 }
299
300 public void testManualServiceFailWhileStopping() throws Exception {
301 ManualSwitchedService service = new ManualSwitchedService();
302 RecordingListener listener = RecordingListener.record(service);
303 service.startAsync();
304 service.notifyStarted();
305 service.stopAsync();
306 service.notifyFailed(EXCEPTION);
307 assertEquals(ImmutableList.of(State.STARTING, State.RUNNING, State.STOPPING, State.FAILED),
308 listener.getStateHistory());
309 }
310
311 public void testManualServiceUnrequestedStop() {
312 ManualSwitchedService service = new ManualSwitchedService();
313
314 service.startAsync();
315
316 service.notifyStarted();
317 assertEquals(State.RUNNING, service.state());
318 assertTrue(service.isRunning());
319 assertFalse(service.doStopCalled);
320
321 service.notifyStopped();
322 assertEquals(State.TERMINATED, service.state());
323 assertFalse(service.isRunning());
324 assertFalse(service.doStopCalled);
325 }
326
327 /**
328 * The user of this service should call {@link #notifyStarted} and {@link
329 * #notifyStopped} after calling {@link #startAsync} and {@link #stopAsync}.
330 */
331 private static class ManualSwitchedService extends AbstractService {
332 boolean doStartCalled = false;
333 boolean doStopCalled = false;
334
335 @Override protected void doStart() {
336 assertFalse(doStartCalled);
337 doStartCalled = true;
338 }
339
340 @Override protected void doStop() {
341 assertFalse(doStopCalled);
342 doStopCalled = true;
343 }
344 }
345
346 public void testAwaitTerminated() throws Exception {
347 final NoOpService service = new NoOpService();
348 Thread waiter = new Thread() {
349 @Override public void run() {
350 service.awaitTerminated();
351 }
352 };
353 waiter.start();
354 service.startAsync().awaitRunning();
355 assertEquals(State.RUNNING, service.state());
356 service.stopAsync();
357 waiter.join(100); // ensure that the await in the other thread is triggered
358 assertFalse(waiter.isAlive());
359 }
360
361 public void testAwaitTerminated_FailedService() throws Exception {
362 final ManualSwitchedService service = new ManualSwitchedService();
363 final AtomicReference<Throwable> exception = Atomics.newReference();
364 Thread waiter = new Thread() {
365 @Override public void run() {
366 try {
367 service.awaitTerminated();
368 fail("Expected an IllegalStateException");
369 } catch (Throwable t) {
370 exception.set(t);
371 }
372 }
373 };
374 waiter.start();
375 service.startAsync();
376 service.notifyStarted();
377 assertEquals(State.RUNNING, service.state());
378 service.notifyFailed(EXCEPTION);
379 assertEquals(State.FAILED, service.state());
380 waiter.join(100);
381 assertFalse(waiter.isAlive());
382 assertTrue(exception.get() instanceof IllegalStateException);
383 assertEquals(EXCEPTION, exception.get().getCause());
384 }
385
386 public void testThreadedServiceStartAndWaitStopAndWait() throws Throwable {
387 ThreadedService service = new ThreadedService();
388 RecordingListener listener = RecordingListener.record(service);
389 service.startAsync().awaitRunning();
390 assertEquals(State.RUNNING, service.state());
391
392 service.awaitRunChecks();
393
394 service.stopAsync().awaitTerminated();
395 assertEquals(State.TERMINATED, service.state());
396
397 throwIfSet(thrownByExecutionThread);
398 assertEquals(
399 ImmutableList.of(
400 State.STARTING,
401 State.RUNNING,
402 State.STOPPING,
403 State.TERMINATED),
404 listener.getStateHistory());
405 }
406
407 public void testThreadedServiceStopIdempotence() throws Throwable {
408 ThreadedService service = new ThreadedService();
409
410 service.startAsync().awaitRunning();
411 assertEquals(State.RUNNING, service.state());
412
413 service.awaitRunChecks();
414
415 service.stopAsync();
416 service.stopAsync().awaitTerminated();
417 assertEquals(State.TERMINATED, service.state());
418
419 throwIfSet(thrownByExecutionThread);
420 }
421
422 public void testThreadedServiceStopIdempotenceAfterWait()
423 throws Throwable {
424 ThreadedService service = new ThreadedService();
425
426 service.startAsync().awaitRunning();
427 assertEquals(State.RUNNING, service.state());
428
429 service.awaitRunChecks();
430
431 service.stopAsync().awaitTerminated();
432 service.stopAsync();
433 assertEquals(State.TERMINATED, service.state());
434
435 executionThread.join();
436
437 throwIfSet(thrownByExecutionThread);
438 }
439
440 public void testThreadedServiceStopIdempotenceDoubleWait()
441 throws Throwable {
442 ThreadedService service = new ThreadedService();
443
444 service.startAsync().awaitRunning();
445 assertEquals(State.RUNNING, service.state());
446
447 service.awaitRunChecks();
448
449 service.stopAsync().awaitTerminated();
450 service.stopAsync().awaitTerminated();
451 assertEquals(State.TERMINATED, service.state());
452
453 throwIfSet(thrownByExecutionThread);
454 }
455
456 public void testManualServiceFailureIdempotence() {
457 ManualSwitchedService service = new ManualSwitchedService();
458 RecordingListener.record(service);
459 service.startAsync();
460 service.notifyFailed(new Exception("1"));
461 service.notifyFailed(new Exception("2"));
462 assertEquals("1", service.failureCause().getMessage());
463 try {
464 service.awaitRunning();
465 fail();
466 } catch (IllegalStateException e) {
467 assertEquals("1", e.getCause().getMessage());
468 }
469 }
470
471 private class ThreadedService extends AbstractService {
472 final CountDownLatch hasConfirmedIsRunning = new CountDownLatch(1);
473
474 /*
475 * The main test thread tries to stop() the service shortly after
476 * confirming that it is running. Meanwhile, the service itself is trying
477 * to confirm that it is running. If the main thread's stop() call happens
478 * before it has the chance, the test will fail. To avoid this, the main
479 * thread calls this method, which waits until the service has performed
480 * its own "running" check.
481 */
482 void awaitRunChecks() throws InterruptedException {
483 assertTrue("Service thread hasn't finished its checks. "
484 + "Exception status (possibly stale): " + thrownByExecutionThread,
485 hasConfirmedIsRunning.await(10, SECONDS));
486 }
487
488 @Override protected void doStart() {
489 assertEquals(State.STARTING, state());
490 invokeOnExecutionThreadForTest(new Runnable() {
491 @Override public void run() {
492 assertEquals(State.STARTING, state());
493 notifyStarted();
494 assertEquals(State.RUNNING, state());
495 hasConfirmedIsRunning.countDown();
496 }
497 });
498 }
499
500 @Override protected void doStop() {
501 assertEquals(State.STOPPING, state());
502 invokeOnExecutionThreadForTest(new Runnable() {
503 @Override public void run() {
504 assertEquals(State.STOPPING, state());
505 notifyStopped();
506 assertEquals(State.TERMINATED, state());
507 }
508 });
509 }
510 }
511
512 private void invokeOnExecutionThreadForTest(Runnable runnable) {
513 executionThread = new Thread(runnable);
514 executionThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
515 @Override
516 public void uncaughtException(Thread thread, Throwable e) {
517 thrownByExecutionThread = e;
518 }
519 });
520 executionThread.start();
521 }
522
523 private static void throwIfSet(Throwable t) throws Throwable {
524 if (t != null) {
525 throw t;
526 }
527 }
528
529 public void testStopUnstartedService() throws Exception {
530 NoOpService service = new NoOpService();
531 RecordingListener listener = RecordingListener.record(service);
532
533 service.stopAsync();
534 assertEquals(State.TERMINATED, service.state());
535
536 try {
537 service.startAsync();
538 fail();
539 } catch (IllegalStateException expected) {}
540 assertEquals(State.TERMINATED, Iterables.getOnlyElement(listener.getStateHistory()));
541 }
542
543 public void testFailingServiceStartAndWait() throws Exception {
544 StartFailingService service = new StartFailingService();
545 RecordingListener listener = RecordingListener.record(service);
546
547 try {
548 service.startAsync().awaitRunning();
549 fail();
550 } catch (IllegalStateException e) {
551 assertEquals(EXCEPTION, service.failureCause());
552 assertEquals(EXCEPTION, e.getCause());
553 }
554 assertEquals(
555 ImmutableList.of(
556 State.STARTING,
557 State.FAILED),
558 listener.getStateHistory());
559 }
560
561 public void testFailingServiceStopAndWait_stopFailing() throws Exception {
562 StopFailingService service = new StopFailingService();
563 RecordingListener listener = RecordingListener.record(service);
564
565 service.startAsync().awaitRunning();
566 try {
567 service.stopAsync().awaitTerminated();
568 fail();
569 } catch (IllegalStateException e) {
570 assertEquals(EXCEPTION, service.failureCause());
571 assertEquals(EXCEPTION, e.getCause());
572 }
573 assertEquals(
574 ImmutableList.of(
575 State.STARTING,
576 State.RUNNING,
577 State.STOPPING,
578 State.FAILED),
579 listener.getStateHistory());
580 }
581
582 public void testFailingServiceStopAndWait_runFailing() throws Exception {
583 RunFailingService service = new RunFailingService();
584 RecordingListener listener = RecordingListener.record(service);
585
586 service.startAsync();
587 try {
588 service.awaitRunning();
589 fail();
590 } catch (IllegalStateException e) {
591 assertEquals(EXCEPTION, service.failureCause());
592 assertEquals(EXCEPTION, e.getCause());
593 }
594 assertEquals(
595 ImmutableList.of(
596 State.STARTING,
597 State.RUNNING,
598 State.FAILED),
599 listener.getStateHistory());
600 }
601
602 public void testThrowingServiceStartAndWait() throws Exception {
603 StartThrowingService service = new StartThrowingService();
604 RecordingListener listener = RecordingListener.record(service);
605
606 try {
607 service.startAsync().awaitRunning();
608 fail();
609 } catch (IllegalStateException e) {
610 assertEquals(service.exception, service.failureCause());
611 assertEquals(service.exception, e.getCause());
612 }
613 assertEquals(
614 ImmutableList.of(
615 State.STARTING,
616 State.FAILED),
617 listener.getStateHistory());
618 }
619
620 public void testThrowingServiceStopAndWait_stopThrowing() throws Exception {
621 StopThrowingService service = new StopThrowingService();
622 RecordingListener listener = RecordingListener.record(service);
623
624 service.startAsync().awaitRunning();
625 try {
626 service.stopAsync().awaitTerminated();
627 fail();
628 } catch (IllegalStateException e) {
629 assertEquals(service.exception, service.failureCause());
630 assertEquals(service.exception, e.getCause());
631 }
632 assertEquals(
633 ImmutableList.of(
634 State.STARTING,
635 State.RUNNING,
636 State.STOPPING,
637 State.FAILED),
638 listener.getStateHistory());
639 }
640
641 public void testThrowingServiceStopAndWait_runThrowing() throws Exception {
642 RunThrowingService service = new RunThrowingService();
643 RecordingListener listener = RecordingListener.record(service);
644
645 service.startAsync();
646 try {
647 service.awaitTerminated();
648 fail();
649 } catch (IllegalStateException e) {
650 assertEquals(service.exception, service.failureCause());
651 assertEquals(service.exception, e.getCause());
652 }
653 assertEquals(
654 ImmutableList.of(
655 State.STARTING,
656 State.RUNNING,
657 State.FAILED),
658 listener.getStateHistory());
659 }
660
661 public void testFailureCause_throwsIfNotFailed() {
662 StopFailingService service = new StopFailingService();
663 try {
664 service.failureCause();
665 fail();
666 } catch (IllegalStateException e) {
667 // expected
668 }
669 service.startAsync().awaitRunning();
670 try {
671 service.failureCause();
672 fail();
673 } catch (IllegalStateException e) {
674 // expected
675 }
676 try {
677 service.stopAsync().awaitTerminated();
678 fail();
679 } catch (IllegalStateException e) {
680 assertEquals(EXCEPTION, service.failureCause());
681 assertEquals(EXCEPTION, e.getCause());
682 }
683 }
684
685 public void testAddListenerAfterFailureDoesntCauseDeadlock() throws InterruptedException {
686 final StartFailingService service = new StartFailingService();
687 service.startAsync();
688 assertEquals(State.FAILED, service.state());
689 service.addListener(new RecordingListener(service), MoreExecutors.sameThreadExecutor());
690 Thread thread = new Thread() {
691 @Override public void run() {
692 // Internally stopAsync() grabs a lock, this could be any such method on AbstractService.
693 service.stopAsync();
694 }
695 };
696 thread.start();
697 thread.join(100);
698 assertFalse(thread + " is deadlocked", thread.isAlive());
699 }
700
701 public void testListenerDoesntDeadlockOnStartAndWaitFromRunning() throws Exception {
702 final NoOpThreadedService service = new NoOpThreadedService();
703 service.addListener(new Listener() {
704 @Override public void running() {
705 service.awaitRunning();
706 }
707 }, MoreExecutors.sameThreadExecutor());
708 service.startAsync().awaitRunning(10, TimeUnit.MILLISECONDS);
709 service.stopAsync();
710 }
711
712 public void testListenerDoesntDeadlockOnStopAndWaitFromTerminated() throws Exception {
713 final NoOpThreadedService service = new NoOpThreadedService();
714 service.addListener(new Listener() {
715 @Override public void terminated(State from) {
716 service.stopAsync().awaitTerminated();
717 }
718 }, MoreExecutors.sameThreadExecutor());
719 service.startAsync().awaitRunning();
720
721 Thread thread = new Thread() {
722 @Override public void run() {
723 service.stopAsync().awaitTerminated();
724 }
725 };
726 thread.start();
727 thread.join(100);
728 assertFalse(thread + " is deadlocked", thread.isAlive());
729 }
730
731 private static class NoOpThreadedService extends AbstractExecutionThreadService {
732 final CountDownLatch latch = new CountDownLatch(1);
733 @Override protected void run() throws Exception {
734 latch.await();
735 }
736 @Override protected void triggerShutdown() {
737 latch.countDown();
738 }
739 }
740
741 private static class StartFailingService extends AbstractService {
742 @Override protected void doStart() {
743 notifyFailed(EXCEPTION);
744 }
745
746 @Override protected void doStop() {
747 fail();
748 }
749 }
750
751 private static class RunFailingService extends AbstractService {
752 @Override protected void doStart() {
753 notifyStarted();
754 notifyFailed(EXCEPTION);
755 }
756
757 @Override protected void doStop() {
758 fail();
759 }
760 }
761
762 private static class StopFailingService extends AbstractService {
763 @Override protected void doStart() {
764 notifyStarted();
765 }
766
767 @Override protected void doStop() {
768 notifyFailed(EXCEPTION);
769 }
770 }
771
772 private static class StartThrowingService extends AbstractService {
773
774 final RuntimeException exception = new RuntimeException("deliberate");
775
776 @Override protected void doStart() {
777 throw exception;
778 }
779
780 @Override protected void doStop() {
781 fail();
782 }
783 }
784
785 private static class RunThrowingService extends AbstractService {
786
787 final RuntimeException exception = new RuntimeException("deliberate");
788
789 @Override protected void doStart() {
790 notifyStarted();
791 throw exception;
792 }
793
794 @Override protected void doStop() {
795 fail();
796 }
797 }
798
799 private static class StopThrowingService extends AbstractService {
800
801 final RuntimeException exception = new RuntimeException("deliberate");
802
803 @Override protected void doStart() {
804 notifyStarted();
805 }
806
807 @Override protected void doStop() {
808 throw exception;
809 }
810 }
811
812 private static class RecordingListener extends Listener {
813 static RecordingListener record(Service service) {
814 RecordingListener listener = new RecordingListener(service);
815 service.addListener(listener, MoreExecutors.sameThreadExecutor());
816 return listener;
817 }
818
819 final Service service;
820
821 RecordingListener(Service service) {
822 this.service = service;
823 }
824
825 @GuardedBy("this")
826 final List<State> stateHistory = Lists.newArrayList();
827 final CountDownLatch completionLatch = new CountDownLatch(1);
828
829 ImmutableList<State> getStateHistory() throws Exception {
830 completionLatch.await();
831 synchronized (this) {
832 return ImmutableList.copyOf(stateHistory);
833 }
834 }
835
836 @Override public synchronized void starting() {
837 assertTrue(stateHistory.isEmpty());
838 assertNotSame(State.NEW, service.state());
839 stateHistory.add(State.STARTING);
840 }
841
842 @Override public synchronized void running() {
843 assertEquals(State.STARTING, Iterables.getOnlyElement(stateHistory));
844 stateHistory.add(State.RUNNING);
845 service.awaitRunning();
846 assertNotSame(State.STARTING, service.state());
847 }
848
849 @Override public synchronized void stopping(State from) {
850 assertEquals(from, Iterables.getLast(stateHistory));
851 stateHistory.add(State.STOPPING);
852 if (from == State.STARTING) {
853 try {
854 service.awaitRunning();
855 fail();
856 } catch (IllegalStateException expected) {
857 assertNull(expected.getCause());
858 assertTrue(expected.getMessage().equals(
859 "Expected the service to be RUNNING, but was STOPPING"));
860 }
861 }
862 assertNotSame(from, service.state());
863 }
864
865 @Override public synchronized void terminated(State from) {
866 assertEquals(from, Iterables.getLast(stateHistory, State.NEW));
867 stateHistory.add(State.TERMINATED);
868 assertEquals(State.TERMINATED, service.state());
869 if (from == State.NEW) {
870 try {
871 service.awaitRunning();
872 fail();
873 } catch (IllegalStateException expected) {
874 assertNull(expected.getCause());
875 assertTrue(expected.getMessage().equals(
876 "Expected the service to be RUNNING, but was TERMINATED"));
877 }
878 }
879 completionLatch.countDown();
880 }
881
882 @Override public synchronized void failed(State from, Throwable failure) {
883 assertEquals(from, Iterables.getLast(stateHistory));
884 stateHistory.add(State.FAILED);
885 assertEquals(State.FAILED, service.state());
886 assertEquals(failure, service.failureCause());
887 if (from == State.STARTING) {
888 try {
889 service.awaitRunning();
890 fail();
891 } catch (IllegalStateException e) {
892 assertEquals(failure, e.getCause());
893 }
894 }
895 try {
896 service.awaitTerminated();
897 fail();
898 } catch (IllegalStateException e) {
899 assertEquals(failure, e.getCause());
900 }
901 completionLatch.countDown();
902 }
903 }
904
905 public void testNotifyStartedWhenNotStarting() {
906 AbstractService service = new DefaultService();
907 try {
908 service.notifyStarted();
909 fail();
910 } catch (IllegalStateException expected) {}
911 }
912
913 public void testNotifyStoppedWhenNotRunning() {
914 AbstractService service = new DefaultService();
915 try {
916 service.notifyStopped();
917 fail();
918 } catch (IllegalStateException expected) {}
919 }
920
921 public void testNotifyFailedWhenNotStarted() {
922 AbstractService service = new DefaultService();
923 try {
924 service.notifyFailed(new Exception());
925 fail();
926 } catch (IllegalStateException expected) {}
927 }
928
929 public void testNotifyFailedWhenTerminated() {
930 NoOpService service = new NoOpService();
931 service.startAsync().awaitRunning();
932 service.stopAsync().awaitTerminated();
933 try {
934 service.notifyFailed(new Exception());
935 fail();
936 } catch (IllegalStateException expected) {}
937 }
938
939 private static class DefaultService extends AbstractService {
940 @Override protected void doStart() {}
941 @Override protected void doStop() {}
942 }
943
944 private static final Exception EXCEPTION = new Exception();
945 }
946 <pre>
ServiceManagerTest view source print?
001 </pre>
002 /*
003 * Copyright (C) 2012 The Guava Authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package com.google.common.util.concurrent;
018
019 import static java.util.Arrays.asList;
020
021 import com.google.common.collect.ImmutableMap;
022 import com.google.common.collect.ImmutableSet;
023 import com.google.common.collect.Lists;
024 import com.google.common.collect.Sets;
025 import com.google.common.testing.NullPointerTester;
026 import com.google.common.testing.TestLogHandler;
027 import com.google.common.util.concurrent.ServiceManager.Listener;
028
029 import junit.framework.TestCase;
030
031 import java.util.Arrays;
032 import java.util.Collection;
033 import java.util.List;
034 import java.util.Set;
035 import java.util.concurrent.CountDownLatch;
036 import java.util.concurrent.Executor;
037 import java.util.concurrent.TimeUnit;
038 import java.util.concurrent.TimeoutException;
039 import java.util.logging.Formatter;
040 import java.util.logging.Level;
041 import java.util.logging.LogRecord;
042 import java.util.logging.Logger;
043
044 /**
045 * Tests for {@link ServiceManager}.
046 *
047 * @author Luke Sandberg
048 * @author Chris Nokleberg
049 */
050 public class ServiceManagerTest extends TestCase {
051
052 private static class NoOpService extends AbstractService {
053 @Override protected void doStart() {
054 notifyStarted();
055 }
056
057 @Override protected void doStop() {
058 notifyStopped();
059 }
060 }
061
062 /*
063 * A NoOp service that will delay the startup and shutdown notification for a configurable amount
064 * of time.
065 */
066 private static class NoOpDelayedSerivce extends NoOpService {
067 private long delay;
068
069 public NoOpDelayedSerivce(long delay) {
070 this.delay = delay;
071 }
072
073 @Override protected void doStart() {
074 new Thread() {
075 @Override public void run() {
076 Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
077 notifyStarted();
078 }
079 }.start();
080 }
081
082 @Override protected void doStop() {
083 new Thread() {
084 @Override public void run() {
085 Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
086 notifyStopped();
087 }
088 }.start();
089 }
090 }
091
092 private static class FailStartService extends NoOpService {
093 @Override protected void doStart() {
094 notifyFailed(new IllegalStateException("failed"));
095 }
096 }
097
098 private static class FailRunService extends NoOpService {
099 @Override protected void doStart() {
100 super.doStart();
101 notifyFailed(new IllegalStateException("failed"));
102 }
103 }
104
105 private static class FailStopService extends NoOpService {
106 @Override protected void doStop() {
107 notifyFailed(new IllegalStateException("failed"));
108 }
109 }
110
111 public void testServiceStartupTimes() {
112 Service a = new NoOpDelayedSerivce(150);
113 Service b = new NoOpDelayedSerivce(353);
114 ServiceManager serviceManager = new ServiceManager(asList(a, b));
115 serviceManager.startAsync().awaitHealthy();
116 ImmutableMap<Service, Long> startupTimes = serviceManager.startupTimes();
117 assertEquals(2, startupTimes.size());
118 assertTrue(startupTimes.get(a) >= 150);
119 assertTrue(startupTimes.get(b) >= 353);
120 }
121
122 public void testServiceStartStop() {
123 Service a = new NoOpService();
124 Service b = new NoOpService();
125 ServiceManager manager = new ServiceManager(asList(a, b));
126 RecordingListener listener = new RecordingListener();
127 manager.addListener(listener);
128 assertState(manager, Service.State.NEW, a, b);
129 assertFalse(manager.isHealthy());
130 manager.startAsync().awaitHealthy();
131 assertState(manager, Service.State.RUNNING, a, b);
132 assertTrue(manager.isHealthy());
133 assertTrue(listener.healthyCalled);
134 assertFalse(listener.stoppedCalled);
135 assertTrue(listener.failedServices.isEmpty());
136 manager.stopAsync().awaitStopped();
137 assertState(manager, Service.State.TERMINATED, a, b);
138 assertFalse(manager.isHealthy());
139 assertTrue(listener.stoppedCalled);
140 assertTrue(listener.failedServices.isEmpty());
141 }
142
143 public void testFailStart() throws Exception {
144 Service a = new NoOpService();
145 Service b = new FailStartService();
146 Service c = new NoOpService();
147 Service d = new FailStartService();
148 Service e = new NoOpService();
149 ServiceManager manager = new ServiceManager(asList(a, b, c, d, e));
150 RecordingListener listener = new RecordingListener();
151 manager.addListener(listener);
152 assertState(manager, Service.State.NEW, a, b, c, d, e);
153 try {
154 manager.startAsync().awaitHealthy();
155 fail();
156 } catch (IllegalStateException expected) {
157 }
158 assertFalse(listener.healthyCalled);
159 assertState(manager, Service.State.RUNNING, a, c, e);
160 assertEquals(ImmutableSet.of(b, d), listener.failedServices);
161 assertState(manager, Service.State.FAILED, b, d);
162 assertFalse(manager.isHealthy());
163
164 manager.stopAsync().awaitStopped();
165 assertFalse(manager.isHealthy());
166 assertFalse(listener.healthyCalled);
167 assertTrue(listener.stoppedCalled);
168 }
169
170 public void testFailRun() throws Exception {
171 Service a = new NoOpService();
172 Service b = new FailRunService();
173 ServiceManager manager = new ServiceManager(asList(a, b));
174 RecordingListener listener = new RecordingListener();
175 manager.addListener(listener);
176 assertState(manager, Service.State.NEW, a, b);
177 try {
178 manager.startAsync().awaitHealthy();
179 fail();
180 } catch (IllegalStateException expected) {
181 }
182 assertTrue(listener.healthyCalled);
183 assertEquals(ImmutableSet.of(b), listener.failedServices);
184
185 manager.stopAsync().awaitStopped();
186 assertState(manager, Service.State.FAILED, b);
187 assertState(manager, Service.State.TERMINATED, a);
188
189 assertTrue(listener.stoppedCalled);
190 }
191
192 public void testFailStop() throws Exception {
193 Service a = new NoOpService();
194 Service b = new FailStopService();
195 Service c = new NoOpService();
196 ServiceManager manager = new ServiceManager(asList(a, b, c));
197 RecordingListener listener = new RecordingListener();
198 manager.addListener(listener);
199
200 manager.startAsync().awaitHealthy();
201 assertTrue(listener.healthyCalled);
202 assertFalse(listener.stoppedCalled);
203 manager.stopAsync().awaitStopped();
204
205 assertTrue(listener.stoppedCalled);
206 assertEquals(ImmutableSet.of(b), listener.failedServices);
207 assertState(manager, Service.State.FAILED, b);
208 assertState(manager, Service.State.TERMINATED, a, c);
209 }
210
211 public void testToString() throws Exception {
212 Service a = new NoOpService();
213 Service b = new FailStartService();
214 ServiceManager manager = new ServiceManager(asList(a, b));
215 String toString = manager.toString();
216 assertTrue(toString.contains("NoOpService"));
217 assertTrue(toString.contains("FailStartService"));
218 }
219
220 public void testTimeouts() throws Exception {
221 Service a = new NoOpDelayedSerivce(50);
222 ServiceManager manager = new ServiceManager(asList(a));
223 manager.startAsync();
224 try {
225 manager.awaitHealthy(1, TimeUnit.MILLISECONDS);
226 fail();
227 } catch (TimeoutException expected) {
228 }
229 manager.awaitHealthy(100, TimeUnit.MILLISECONDS); // no exception thrown
230
231 manager.stopAsync();
232 try {
233 manager.awaitStopped(1, TimeUnit.MILLISECONDS);
234 fail();
235 } catch (TimeoutException expected) {
236 }
237 manager.awaitStopped(100, TimeUnit.MILLISECONDS); // no exception thrown
238 }
239
240 /**
241 * This covers a case where if the last service to stop failed then the stopped callback would
242 * never be called.
243 */
244 public void testSingleFailedServiceCallsStopped() {
245 Service a = new FailStartService();
246 ServiceManager manager = new ServiceManager(asList(a));
247 RecordingListener listener = new RecordingListener();
248 manager.addListener(listener);
249 try {
250 manager.startAsync().awaitHealthy();
251 fail();
252 } catch (IllegalStateException expected) {
253 }
254 assertTrue(listener.stoppedCalled);
255 }
256
257 /**
258 * This covers a bug where listener.healthy would get called when a single service failed during
259 * startup (it occurred in more complicated cases also).
260 */
261 public void testFailStart_singleServiceCallsHealthy() {
262 Service a = new FailStartService();
263 ServiceManager manager = new ServiceManager(asList(a));
264 RecordingListener listener = new RecordingListener();
265 manager.addListener(listener);
266 try {
267 manager.startAsync().awaitHealthy();
268 fail();
269 } catch (IllegalStateException expected) {
270 }
271 assertFalse(listener.healthyCalled);
272 }
273
274 /**
275 * This covers a bug where if a listener was installed that would stop the manager if any service
276 * fails and something failed during startup before service.start was called on all the services,
277 * then awaitStopped would deadlock due to an IllegalStateException that was thrown when trying to
278 * stop the timer(!).
279 */
280 public void testFailStart_stopOthers() throws TimeoutException {
281 Service a = new FailStartService();
282 Service b = new NoOpService();
283 final ServiceManager manager = new ServiceManager(asList(a, b));
284 manager.addListener(new Listener() {
285 @Override public void failure(Service service) {
286 manager.stopAsync();
287 }});
288 manager.startAsync();
289 manager.awaitStopped(10, TimeUnit.MILLISECONDS);
290 }
291
292 private static void assertState(
293 ServiceManager manager, Service.State state, Service... services) {
294 Collection<Service> managerServices = manager.servicesByState().get(state);
295 for (Service service : services) {
296 assertEquals(service.toString(), state, service.state());
297 assertEquals(service.toString(), service.isRunning(), state == Service.State.RUNNING);
298 assertTrue(managerServices + " should contain " + service, managerServices.contains(service));
299 }
300 }
301
302 /**
303 * This is for covering a case where the ServiceManager would behave strangely if constructed
304 * with no service under management. Listeners would never fire because the ServiceManager was
305 * healthy and stopped at the same time. This test ensures that listeners fire and isHealthy
306 * makes sense.
307 */
308 public void testEmptyServiceManager() {
309 Logger logger = Logger.getLogger(ServiceManager.class.getName());
310 logger.setLevel(Level.FINEST);
311 TestLogHandler logHandler = new TestLogHandler();
312 logger.addHandler(logHandler);
313 ServiceManager manager = new ServiceManager(Arrays.<Service>asList());
314 RecordingListener listener = new RecordingListener();
315 manager.addListener(listener, MoreExecutors.sameThreadExecutor());
316 manager.startAsync().awaitHealthy();
317 assertTrue(manager.isHealthy());
318 assertTrue(listener.healthyCalled);
319 assertFalse(listener.stoppedCalled);
320 assertTrue(listener.failedServices.isEmpty());
321 manager.stopAsync().awaitStopped();
322 assertFalse(manager.isHealthy());
323 assertTrue(listener.stoppedCalled);
324 assertTrue(listener.failedServices.isEmpty());
325 // check that our NoOpService is not directly observable via any of the inspection methods or
326 // via logging.
327 assertEquals("ServiceManager{services=[]}", manager.toString());
328 assertTrue(manager.servicesByState().isEmpty());
329 assertTrue(manager.startupTimes().isEmpty());
330 Formatter logFormatter = new Formatter() {
331 @Override public String format(LogRecord record) {
332 return formatMessage(record);
333 }
334 };
335 for (LogRecord record : logHandler.getStoredLogRecords()) {
336 assertFalse(logFormatter.format(record).contains("NoOpService"));
337 }
338 }
339
340 /**
341 * This is for a case where a long running Listener using the sameThreadListener could deadlock
342 * another thread calling stopAsync().
343 */
344
345 public void testListenerDeadlock() throws InterruptedException {
346 final CountDownLatch failEnter = new CountDownLatch(1);
347 Service failRunService = new AbstractService() {
348 @Override protected void doStart() {
349 new Thread() {
350 @Override public void run() {
351 notifyStarted();
352 notifyFailed(new Exception("boom"));
353 }
354 }.start();
355 }
356 @Override protected void doStop() {
357 notifyStopped();
358 }
359 };
360 final ServiceManager manager = new ServiceManager(
361 Arrays.asList(failRunService, new NoOpService()));
362 manager.addListener(new ServiceManager.Listener() {
363 @Override public void failure(Service service) {
364 failEnter.countDown();
365 // block forever!
366 Uninterruptibles.awaitUninterruptibly(new CountDownLatch(1));
367 }
368 }, MoreExecutors.sameThreadExecutor());
369 // We do not call awaitHealthy because, due to races, that method may throw an exception. But
370 // we really just want to wait for the thread to be in the failure callback so we wait for that
371 // explicitly instead.
372 manager.startAsync();
373 failEnter.await();
374 assertFalse("State should be updated before calling listeners", manager.isHealthy());
375 // now we want to stop the services.
376 Thread stoppingThread = new Thread() {
377 @Override public void run() {
378 manager.stopAsync().awaitStopped();
379 }
380 };
381 stoppingThread.start();
382 // this should be super fast since the only non stopped service is a NoOpService
383 stoppingThread.join(1000);
384 assertFalse("stopAsync has deadlocked!.", stoppingThread.isAlive());
385 }
386
387 /**
388 * Catches a bug where when constructing a service manager failed, later interactions with the
389 * service could cause IllegalStateExceptions inside the partially constructed ServiceManager.
390 * This ISE wouldn't actually bubble up but would get logged by ExecutionQueue. This obfuscated
391 * the original error (which was not constructing ServiceManager correctly).
392 */
393 public void testPartiallyConstructedManager() {
394 Logger logger = Logger.getLogger("global");
395 logger.setLevel(Level.FINEST);
396 TestLogHandler logHandler = new TestLogHandler();
397 logger.addHandler(logHandler);
398 NoOpService service = new NoOpService();
399 service.startAsync();
400 try {
401 new ServiceManager(Arrays.asList(service));
402 fail();
403 } catch (IllegalArgumentException expected) {}
404 service.stopAsync();
405 // Nothing was logged!
406 assertEquals(0, logHandler.getStoredLogRecords().size());
407 }
408
409 public void testPartiallyConstructedManager_transitionAfterAddListenerBeforeStateIsReady() {
410 // The implementation of this test is pretty sensitive to the implementation :( but we want to
411 // ensure that if weird things happen during construction then we get exceptions.
412 final NoOpService service1 = new NoOpService();
413 // This service will start service1 when addListener is called. This simulates service1 being
414 // started asynchronously.
415 Service service2 = new Service() {
416 final NoOpService delegate = new NoOpService();
417 @Override public final void addListener(Listener listener, Executor executor) {
418 service1.startAsync();
419 delegate.addListener(listener, executor);
420 }
421 // Delegates from here on down
422 @Override public final Service startAsync() {
423 return delegate.startAsync();
424 }
425
426 @Override public final Service stopAsync() {
427 return delegate.stopAsync();
428 }
429
430 @Override public final ListenableFuture<State> start() {
431 return delegate.start();
432 }
433
434 @Override public final ListenableFuture<State> stop() {
435 return delegate.stop();
436 }
437
438 @Override public State startAndWait() {
439 return delegate.startAndWait();
440 }
441
442 @Override public State stopAndWait() {
443 return delegate.stopAndWait();
444 }
445
446 @Override public final void awaitRunning() {
447 delegate.awaitRunning();
448 }
449
450 @Override public final void awaitRunning(long timeout, TimeUnit unit)
451 throws TimeoutException {
452 delegate.awaitRunning(timeout, unit);
453 }
454
455 @Override public final void awaitTerminated() {
456 delegate.awaitTerminated();
457 }
458
459 @Override public final void awaitTerminated(long timeout, TimeUnit unit)
460 throws TimeoutException {
461 delegate.awaitTerminated(timeout, unit);
462 }
463
464 @Override public final boolean isRunning() {
465 return delegate.isRunning();
466 }
467
468 @Override public final State state() {
469 return delegate.state();
470 }
471
472 @Override public final Throwable failureCause() {
473 return delegate.failureCause();
474 }
475 };
476 try {
477 new ServiceManager(Arrays.asList(service1, service2));
478 fail();
479 } catch (IllegalArgumentException expected) {
480 assertTrue(expected.getMessage().contains("started transitioning asynchronously"));
481 }
482 }
483
484 /**
485 * This test is for a case where two Service.Listener callbacks for the same service would call
486 * transitionService in the wrong order due to a race. Due to the fact that it is a race this
487 * test isn't guaranteed to expose the issue, but it is at least likely to become flaky if the
488 * race sneaks back in, and in this case flaky means something is definitely wrong.
489 *
490 * <p>Before the bug was fixed this test would fail at least 30% of the time.
491 */
492
493 public void testTransitionRace() throws TimeoutException {
494 for (int k = 0; k < 1000; k++) {
495 List<Service> services = Lists.newArrayList();
496 for (int i = 0; i < 5; i++) {
497 services.add(new SnappyShutdownService(i));
498 }
499 ServiceManager manager = new ServiceManager(services);
500 manager.startAsync().awaitHealthy();
501 manager.stopAsync().awaitStopped(1, TimeUnit.SECONDS);
502 }
503 }
504
505 /**
506 * This service will shutdown very quickly after stopAsync is called and uses a background thread
507 * so that we know that the stopping() listeners will execute on a different thread than the
508 * terminated() listeners.
509 */
510 private static class SnappyShutdownService extends AbstractExecutionThreadService {
511 final int index;
512 final CountDownLatch latch = new CountDownLatch(1);
513
514 SnappyShutdownService(int index) {
515 this.index = index;
516 }
517
518 @Override protected void run() throws Exception {
519 latch.await();
520 }
521
522 @Override protected void triggerShutdown() {
523 latch.countDown();
524 }
525
526 @Override protected String serviceName() {
527 return this.getClass().getSimpleName() + "[" + index + "]";
528 }
529 }
530
531 public void testNulls() {
532 ServiceManager manager = new ServiceManager(Arrays.<Service>asList());
533 new NullPointerTester()
534 .setDefault(ServiceManager.Listener.class, new RecordingListener())
535 .testAllPublicInstanceMethods(manager);
536 }
537
538 private static final class RecordingListener extends ServiceManager.Listener {
539 volatile boolean healthyCalled;
540 volatile boolean stoppedCalled;
541 final Set<Service> failedServices = Sets.newConcurrentHashSet();
542
543 @Override public void healthy() {
544 healthyCalled = true;
545 }
546
547 @Override public void stopped() {
548 stoppedCalled = true;
549 }
550
551 @Override public void failure(Service service) {
552 failedServices.add(service);
553 }
554 }
555 }
556 <pre>
Google-Guava Concurrent包里的Service框架浅析的更多相关文章
- concurrent包分析之Executor框架
文章目录 线程生命周期的开销:线程比较少的情况使用new Thread(task)无多大影响,但是如果涉及到线程比较多的情况,应用的性能就会受到影响,如果jdbc创建连接一样,new Thead创建线 ...
- Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
1:本例介绍一个特殊的队列:BlockingQueue,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会 ...
- java.util.concurrent包
在JavaSE5中,JUC(java.util.concurrent)包出现了 在java.util.concurrent包及其子包中,有了很多好玩的新东西: 1.执行器的概念和线程池的实现.Exec ...
- 使用 Google Guava 美化你的 Java 代码
文章转载自:http://my.oschina.net/leejun2005/blog/172328 目录:[ - ] 1-使用 GOOGLE COLLECTIONS,GUAVA,STATIC IMP ...
- 并发知识与concurrent包
要想进入一线互联网公司,这部分内容必须要会,否则的话,你始终都只能停留在比较low的段位. 关于并发知识,最重要的两个概念一定要搞清楚,那就是可见性和原子性.其中可见性与前面提到的volatile关键 ...
- 用java自制简易线程池(不依赖concurrent包)
很久之前人们为了继续享用并行化带来的好处而不想使用进程,于是创造出了比进程更轻量级的线程.以linux为例,创建一个进程需要申请新的自己的内存空间,从父进程拷贝一些数据,所以开销是比较大的,线程(或称 ...
- Java 并发工具箱之concurrent包
概述 java.util.concurrent 包是专为 Java并发编程而设计的包.包下的所有类可以分为如下几大类: locks部分:显式锁(互斥锁和速写锁)相关: atomic部分:原子变量类相关 ...
- com.google.guava 包解析 ——Google Guava官方教程(中文版)
全网址 http://ifeve.com/google-guava/ 竹子博客: http://www.cnblogs.com/peida/archive/2013/06/08/ ...
- java.util.concurrent包学习笔记(一)Executor框架
类图: 其实从类图我们能发现concurrent包(除去java.util.concurrent.atomic 和 java.util.concurrent.locks)中的内容并没有特别多,大概分为 ...
随机推荐
- windows下编译libevent(2.1.8)及使用
一:获取libevent github地址:https://github.com/libevent/libevent/releases 下载2.1.8稳定版 二:编译libevent 我是用的visu ...
- 序列变换(HDU-5256)【LIS】
题目链接:https://vjudge.net/problem/HDU-5256 题意:给一个数列,每一个数都不相同且为整数,现求,最少需要修改多少次才能使该数列为严格上升的. 思路:首先,对于一个严 ...
- 通过jquery触发select自身的change事件
###通过jquery触发select自身的change事件 1.通过js来去触发select的change事件 代码如下:包含了html部分和js部分 //html部分 <select cla ...
- Python标准库之sched模块介绍
sched——通用时间调度器 sched模块实现了一个通用事件调度器,在调度器类使用一个延迟函数等待特定的时间,执行任务.同时支持多线程应用程序,在每个任务执行后会立刻调用延时函数,以确保其他线程也能 ...
- 关于Basic Latin踩到的一些坑
目录 在wiki中,很多语言的字符集都包含了Basic Latin,一开始我没有细看,以为Basic Latin里面都是正常的字符集,后来在线上环境出现了问题 博主某天接到一个需求,需要过滤出某国语言 ...
- 使用Duilib开发Windows软件(4)——消息传递
云信Duilib中没有窗体类的函数可以用来直接收取到所有控件的事件,每个控件都可以单独设置自己的事件处理函数,一般在InitWindow方法中初始化各个控件的事件处理函数. 每个控件都有许多形如Att ...
- SAS学习笔记23 线性回归、多元回归
线性回归 由样本资料计算的回归系数b和其他统计量一样,存在抽样误差,因此,需要对线性回归方程进行假设检验 1.方差分析 2.t检验 相关系数的假设检验 相关系数(correlation coeffic ...
- 深度剖析Kubernetes API Server三部曲 - part 1
欢迎来到深入学习Kubernetes API Server的系列文章,在本系列文章中我们将深入的探究Kubernetes API Server的相关实现.如果你对Kubernetes 的内部实现机制比 ...
- 使用vue-cli创建vue工程
在Windows环境下,打开命令行窗口,跳转至想创建工程的路径. 如:D:\MyWork\22_Github\rexel-cn\rexel-jarvis 创建vue工程,命令:vue create r ...
- linux 下phpmyadmin 安装
系统 parrot os(Debain) php7.0 安装phpmyadmin sudo apt install phpmyadmin 然后将/usr/shar/phpmyadmin软连至/va ...