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