1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.google.common.util.concurrent;
17
18 import static com.google.common.truth.Truth.assertThat;
19 import static java.util.Arrays.asList;
20
21 import com.google.common.collect.ImmutableMap;
22 import com.google.common.collect.ImmutableSet;
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Sets;
25 import com.google.common.testing.NullPointerTester;
26 import com.google.common.testing.TestLogHandler;
27 import com.google.common.util.concurrent.ServiceManager.Listener;
28
29 import junit.framework.TestCase;
30
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.Set;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.Executor;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
39 import java.util.logging.Formatter;
40 import java.util.logging.Level;
41 import java.util.logging.LogRecord;
42 import java.util.logging.Logger;
43
44
45
46
47
48
49
50 public class ServiceManagerTest extends TestCase {
51
52 private static class NoOpService extends AbstractService {
53 @Override protected void doStart() {
54 notifyStarted();
55 }
56
57 @Override protected void doStop() {
58 notifyStopped();
59 }
60 }
61
62
63
64
65
66 private static class NoOpDelayedService extends NoOpService {
67 private long delay;
68
69 public NoOpDelayedService(long delay) {
70 this.delay = delay;
71 }
72
73 @Override protected void doStart() {
74 new Thread() {
75 @Override public void run() {
76 Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
77 notifyStarted();
78 }
79 }.start();
80 }
81
82 @Override protected void doStop() {
83 new Thread() {
84 @Override public void run() {
85 Uninterruptibles.sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
86 notifyStopped();
87 }
88 }.start();
89 }
90 }
91
92 private static class FailStartService extends NoOpService {
93 @Override protected void doStart() {
94 notifyFailed(new IllegalStateException("failed"));
95 }
96 }
97
98 private static class FailRunService extends NoOpService {
99 @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 NoOpDelayedService(150);
113 Service b = new NoOpDelayedService(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 assertThat(startupTimes.get(a)).isInclusivelyInRange(150, Long.MAX_VALUE);
119 assertThat(startupTimes.get(b)).isInclusivelyInRange(353, Long.MAX_VALUE);
120 }
121
122 public void testServiceStartupTimes_selfStartingServices() {
123
124
125
126
127 final Service b = new NoOpDelayedService(353) {
128 @Override protected void doStart() {
129 super.doStart();
130
131 Uninterruptibles.sleepUninterruptibly(150, TimeUnit.MILLISECONDS);
132 }
133 };
134 Service a = new NoOpDelayedService(150) {
135 @Override protected void doStart() {
136 b.startAsync();
137 super.doStart();
138 }
139 };
140 ServiceManager serviceManager = new ServiceManager(asList(a, b));
141 serviceManager.startAsync().awaitHealthy();
142 ImmutableMap<Service, Long> startupTimes = serviceManager.startupTimes();
143 assertEquals(2, startupTimes.size());
144 assertThat(startupTimes.get(a)).isInclusivelyInRange(150, Long.MAX_VALUE);
145
146
147
148
149 assertThat(startupTimes.get(b)).isNotNull();
150 }
151
152 public void testServiceStartStop() {
153 Service a = new NoOpService();
154 Service b = new NoOpService();
155 ServiceManager manager = new ServiceManager(asList(a, b));
156 RecordingListener listener = new RecordingListener();
157 manager.addListener(listener);
158 assertState(manager, Service.State.NEW, a, b);
159 assertFalse(manager.isHealthy());
160 manager.startAsync().awaitHealthy();
161 assertState(manager, Service.State.RUNNING, a, b);
162 assertTrue(manager.isHealthy());
163 assertTrue(listener.healthyCalled);
164 assertFalse(listener.stoppedCalled);
165 assertTrue(listener.failedServices.isEmpty());
166 manager.stopAsync().awaitStopped();
167 assertState(manager, Service.State.TERMINATED, a, b);
168 assertFalse(manager.isHealthy());
169 assertTrue(listener.stoppedCalled);
170 assertTrue(listener.failedServices.isEmpty());
171 }
172
173 public void testFailStart() throws Exception {
174 Service a = new NoOpService();
175 Service b = new FailStartService();
176 Service c = new NoOpService();
177 Service d = new FailStartService();
178 Service e = new NoOpService();
179 ServiceManager manager = new ServiceManager(asList(a, b, c, d, e));
180 RecordingListener listener = new RecordingListener();
181 manager.addListener(listener);
182 assertState(manager, Service.State.NEW, a, b, c, d, e);
183 try {
184 manager.startAsync().awaitHealthy();
185 fail();
186 } catch (IllegalStateException expected) {
187 }
188 assertFalse(listener.healthyCalled);
189 assertState(manager, Service.State.RUNNING, a, c, e);
190 assertEquals(ImmutableSet.of(b, d), listener.failedServices);
191 assertState(manager, Service.State.FAILED, b, d);
192 assertFalse(manager.isHealthy());
193
194 manager.stopAsync().awaitStopped();
195 assertFalse(manager.isHealthy());
196 assertFalse(listener.healthyCalled);
197 assertTrue(listener.stoppedCalled);
198 }
199
200 public void testFailRun() throws Exception {
201 Service a = new NoOpService();
202 Service b = new FailRunService();
203 ServiceManager manager = new ServiceManager(asList(a, b));
204 RecordingListener listener = new RecordingListener();
205 manager.addListener(listener);
206 assertState(manager, Service.State.NEW, a, b);
207 try {
208 manager.startAsync().awaitHealthy();
209 fail();
210 } catch (IllegalStateException expected) {
211 }
212 assertTrue(listener.healthyCalled);
213 assertEquals(ImmutableSet.of(b), listener.failedServices);
214
215 manager.stopAsync().awaitStopped();
216 assertState(manager, Service.State.FAILED, b);
217 assertState(manager, Service.State.TERMINATED, a);
218
219 assertTrue(listener.stoppedCalled);
220 }
221
222 public void testFailStop() throws Exception {
223 Service a = new NoOpService();
224 Service b = new FailStopService();
225 Service c = new NoOpService();
226 ServiceManager manager = new ServiceManager(asList(a, b, c));
227 RecordingListener listener = new RecordingListener();
228 manager.addListener(listener);
229
230 manager.startAsync().awaitHealthy();
231 assertTrue(listener.healthyCalled);
232 assertFalse(listener.stoppedCalled);
233 manager.stopAsync().awaitStopped();
234
235 assertTrue(listener.stoppedCalled);
236 assertEquals(ImmutableSet.of(b), listener.failedServices);
237 assertState(manager, Service.State.FAILED, b);
238 assertState(manager, Service.State.TERMINATED, a, c);
239 }
240
241 public void testToString() throws Exception {
242 Service a = new NoOpService();
243 Service b = new FailStartService();
244 ServiceManager manager = new ServiceManager(asList(a, b));
245 String toString = manager.toString();
246 assertTrue(toString.contains("NoOpService"));
247 assertTrue(toString.contains("FailStartService"));
248 }
249
250 public void testTimeouts() throws Exception {
251 Service a = new NoOpDelayedService(50);
252 ServiceManager manager = new ServiceManager(asList(a));
253 manager.startAsync();
254 try {
255 manager.awaitHealthy(1, TimeUnit.MILLISECONDS);
256 fail();
257 } catch (TimeoutException expected) {
258 }
259 manager.awaitHealthy(100, TimeUnit.MILLISECONDS);
260
261 manager.stopAsync();
262 try {
263 manager.awaitStopped(1, TimeUnit.MILLISECONDS);
264 fail();
265 } catch (TimeoutException expected) {
266 }
267 manager.awaitStopped(100, TimeUnit.MILLISECONDS);
268 }
269
270
271
272
273
274 public void testSingleFailedServiceCallsStopped() {
275 Service a = new FailStartService();
276 ServiceManager manager = new ServiceManager(asList(a));
277 RecordingListener listener = new RecordingListener();
278 manager.addListener(listener);
279 try {
280 manager.startAsync().awaitHealthy();
281 fail();
282 } catch (IllegalStateException expected) {
283 }
284 assertTrue(listener.stoppedCalled);
285 }
286
287
288
289
290
291 public void testFailStart_singleServiceCallsHealthy() {
292 Service a = new FailStartService();
293 ServiceManager manager = new ServiceManager(asList(a));
294 RecordingListener listener = new RecordingListener();
295 manager.addListener(listener);
296 try {
297 manager.startAsync().awaitHealthy();
298 fail();
299 } catch (IllegalStateException expected) {
300 }
301 assertFalse(listener.healthyCalled);
302 }
303
304
305
306
307
308
309
310 public void testFailStart_stopOthers() throws TimeoutException {
311 Service a = new FailStartService();
312 Service b = new NoOpService();
313 final ServiceManager manager = new ServiceManager(asList(a, b));
314 manager.addListener(new Listener() {
315 @Override public void failure(Service service) {
316 manager.stopAsync();
317 }});
318 manager.startAsync();
319 manager.awaitStopped(10, TimeUnit.MILLISECONDS);
320 }
321
322 private static void assertState(
323 ServiceManager manager, Service.State state, Service... services) {
324 Collection<Service> managerServices = manager.servicesByState().get(state);
325 for (Service service : services) {
326 assertEquals(service.toString(), state, service.state());
327 assertEquals(service.toString(), service.isRunning(), state == Service.State.RUNNING);
328 assertTrue(managerServices + " should contain " + service, managerServices.contains(service));
329 }
330 }
331
332
333
334
335
336
337
338 public void testEmptyServiceManager() {
339 Logger logger = Logger.getLogger(ServiceManager.class.getName());
340 logger.setLevel(Level.FINEST);
341 TestLogHandler logHandler = new TestLogHandler();
342 logger.addHandler(logHandler);
343 ServiceManager manager = new ServiceManager(Arrays.<Service>asList());
344 RecordingListener listener = new RecordingListener();
345 manager.addListener(listener);
346 manager.startAsync().awaitHealthy();
347 assertTrue(manager.isHealthy());
348 assertTrue(listener.healthyCalled);
349 assertFalse(listener.stoppedCalled);
350 assertTrue(listener.failedServices.isEmpty());
351 manager.stopAsync().awaitStopped();
352 assertFalse(manager.isHealthy());
353 assertTrue(listener.stoppedCalled);
354 assertTrue(listener.failedServices.isEmpty());
355
356
357 assertEquals("ServiceManager{services=[]}", manager.toString());
358 assertTrue(manager.servicesByState().isEmpty());
359 assertTrue(manager.startupTimes().isEmpty());
360 Formatter logFormatter = new Formatter() {
361 @Override public String format(LogRecord record) {
362 return formatMessage(record);
363 }
364 };
365 for (LogRecord record : logHandler.getStoredLogRecords()) {
366 assertFalse(logFormatter.format(record).contains("NoOpService"));
367 }
368 }
369
370
371
372
373
374
375 public void testListenerDeadlock() throws InterruptedException {
376 final CountDownLatch failEnter = new CountDownLatch(1);
377 final CountDownLatch failLeave = new CountDownLatch(1);
378 final CountDownLatch afterStarted = new CountDownLatch(1);
379 Service failRunService = new AbstractService() {
380 @Override protected void doStart() {
381 new Thread() {
382 @Override public void run() {
383 notifyStarted();
384
385
386 Uninterruptibles.awaitUninterruptibly(afterStarted);
387 notifyFailed(new Exception("boom"));
388 }
389 }.start();
390 }
391 @Override protected void doStop() {
392 notifyStopped();
393 }
394 };
395 final ServiceManager manager = new ServiceManager(
396 Arrays.asList(failRunService, new NoOpService()));
397 manager.addListener(new ServiceManager.Listener() {
398 @Override public void failure(Service service) {
399 failEnter.countDown();
400
401 Uninterruptibles.awaitUninterruptibly(failLeave);
402 }
403 });
404 manager.startAsync();
405 afterStarted.countDown();
406
407
408
409 failEnter.await();
410 assertFalse("State should be updated before calling listeners", manager.isHealthy());
411
412 Thread stoppingThread = new Thread() {
413 @Override public void run() {
414 manager.stopAsync().awaitStopped();
415 }
416 };
417 stoppingThread.start();
418
419 stoppingThread.join(1000);
420 assertFalse("stopAsync has deadlocked!.", stoppingThread.isAlive());
421 failLeave.countDown();
422 }
423
424
425
426
427
428
429
430 public void testPartiallyConstructedManager() {
431 Logger logger = Logger.getLogger("global");
432 logger.setLevel(Level.FINEST);
433 TestLogHandler logHandler = new TestLogHandler();
434 logger.addHandler(logHandler);
435 NoOpService service = new NoOpService();
436 service.startAsync();
437 try {
438 new ServiceManager(Arrays.asList(service));
439 fail();
440 } catch (IllegalArgumentException expected) {}
441 service.stopAsync();
442
443 assertEquals(0, logHandler.getStoredLogRecords().size());
444 }
445
446 public void testPartiallyConstructedManager_transitionAfterAddListenerBeforeStateIsReady() {
447
448
449 final NoOpService service1 = new NoOpService();
450
451
452 Service service2 = new Service() {
453 final NoOpService delegate = new NoOpService();
454 @Override public final void addListener(Listener listener, Executor executor) {
455 service1.startAsync();
456 delegate.addListener(listener, executor);
457 }
458
459 @Override public final Service startAsync() {
460 return delegate.startAsync();
461 }
462
463 @Override public final Service stopAsync() {
464 return delegate.stopAsync();
465 }
466
467 @Override public final void awaitRunning() {
468 delegate.awaitRunning();
469 }
470
471 @Override public final void awaitRunning(long timeout, TimeUnit unit)
472 throws TimeoutException {
473 delegate.awaitRunning(timeout, unit);
474 }
475
476 @Override public final void awaitTerminated() {
477 delegate.awaitTerminated();
478 }
479
480 @Override public final void awaitTerminated(long timeout, TimeUnit unit)
481 throws TimeoutException {
482 delegate.awaitTerminated(timeout, unit);
483 }
484
485 @Override public final boolean isRunning() {
486 return delegate.isRunning();
487 }
488
489 @Override public final State state() {
490 return delegate.state();
491 }
492
493 @Override public final Throwable failureCause() {
494 return delegate.failureCause();
495 }
496 };
497 try {
498 new ServiceManager(Arrays.asList(service1, service2));
499 fail();
500 } catch (IllegalArgumentException expected) {
501 assertTrue(expected.getMessage().contains("started transitioning asynchronously"));
502 }
503 }
504
505
506
507
508
509
510
511
512
513
514 public void testTransitionRace() throws TimeoutException {
515 for (int k = 0; k < 1000; k++) {
516 List<Service> services = Lists.newArrayList();
517 for (int i = 0; i < 5; i++) {
518 services.add(new SnappyShutdownService(i));
519 }
520 ServiceManager manager = new ServiceManager(services);
521 manager.startAsync().awaitHealthy();
522 manager.stopAsync().awaitStopped(1, TimeUnit.SECONDS);
523 }
524 }
525
526
527
528
529
530
531 private static class SnappyShutdownService extends AbstractExecutionThreadService {
532 final int index;
533 final CountDownLatch latch = new CountDownLatch(1);
534
535 SnappyShutdownService(int index) {
536 this.index = index;
537 }
538
539 @Override protected void run() throws Exception {
540 latch.await();
541 }
542
543 @Override protected void triggerShutdown() {
544 latch.countDown();
545 }
546
547 @Override protected String serviceName() {
548 return this.getClass().getSimpleName() + "[" + index + "]";
549 }
550 }
551
552 public void testNulls() {
553 ServiceManager manager = new ServiceManager(Arrays.<Service>asList());
554 new NullPointerTester()
555 .setDefault(ServiceManager.Listener.class, new RecordingListener())
556 .testAllPublicInstanceMethods(manager);
557 }
558
559 private static final class RecordingListener extends ServiceManager.Listener {
560 volatile boolean healthyCalled;
561 volatile boolean stoppedCalled;
562 final Set<Service> failedServices = Sets.newConcurrentHashSet();
563
564 @Override public void healthy() {
565 healthyCalled = true;
566 }
567
568 @Override public void stopped() {
569 stoppedCalled = true;
570 }
571
572 @Override public void failure(Service service) {
573 failedServices.add(service);
574 }
575 }
576 }