1 /* 2 * Copyright (C) 2009 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.util.concurrent; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.base.Preconditions.checkState; 21 import com.google.common.base.Service; 22 import com.google.common.base.Service.State; // javadoc needs this 23 import com.google.common.base.Throwables; 24 25 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.ExecutionException; 27 import java.util.concurrent.Future; 28 import java.util.concurrent.TimeUnit; 29 import java.util.concurrent.TimeoutException; 30 import java.util.concurrent.locks.ReentrantLock; 31 32 /** 33 * Base class for implementing services that can handle {@link #doStart} and 34 * {@link #doStop} requests, responding to them with {@link #notifyStarted()} 35 * and {@link #notifyStopped()} callbacks. Its subclasses must manage threads 36 * manually; consider {@link AbstractExecutionThreadService} if you need only a 37 * single execution thread. 38 * 39 * @author Jesse Wilson 40 * @since 2009.09.15 <b>tentative</b> 41 */ 42 public abstract class AbstractService implements Service { 43 44 private final ReentrantLock lock = new ReentrantLock(); 45 46 private final Transition startup = new Transition(); 47 private final Transition shutdown = new Transition(); 48 49 /** 50 * The internal state, which equals external state unless 51 * shutdownWhenStartupFinishes is true. Guarded by {@code lock}. 52 */ 53 private State state = State.NEW; 54 55 /** 56 * If true, the user requested a shutdown while the service was still starting 57 * up. Guarded by {@code lock}. 58 */ 59 private boolean shutdownWhenStartupFinishes = false; 60 61 /** 62 * This method is called by {@link #start} to initiate service startup. The 63 * invocation of this method should cause a call to {@link #notifyStarted()}, 64 * either during this method's run, or after it has returned. If startup 65 * fails, the invocation should cause a call to {@link 66 * #notifyFailed(Throwable)} instead. 67 * 68 * <p>This method should return promptly; prefer to do work on a different 69 * thread where it is convenient. It is invoked exactly once on service 70 * startup, even when {@link #start} is called multiple times. 71 */ 72 protected abstract void doStart(); 73 74 /** 75 * This method should be used to initiate service shutdown. The invocation 76 * of this method should cause a call to {@link #notifyStopped()}, either 77 * during this method's run, or after it has returned. If shutdown fails, the 78 * invocation should cause a call to {@link #notifyFailed(Throwable)} instead. 79 * 80 * <p>This method should return promptly; prefer to do work on a different 81 * thread where it is convenient. It is invoked exactly once on service 82 * shutdown, even when {@link #stop} is called multiple times. 83 */ 84 protected abstract void doStop(); 85 86 public final Future<State> start() { 87 lock.lock(); 88 try { 89 if (state == State.NEW) { 90 state = State.STARTING; 91 doStart(); 92 } 93 } catch (Throwable startupFailure) { 94 // put the exception in the future, the user can get it via Future.get() 95 notifyFailed(startupFailure); 96 } finally { 97 lock.unlock(); 98 } 99 100 return startup; 101 } 102 103 public final Future<State> stop() { 104 lock.lock(); 105 try { 106 if (state == State.NEW) { 107 state = State.TERMINATED; 108 startup.transitionSucceeded(State.TERMINATED); 109 shutdown.transitionSucceeded(State.TERMINATED); 110 } else if (state == State.STARTING) { 111 shutdownWhenStartupFinishes = true; 112 startup.transitionSucceeded(State.STOPPING); 113 } else if (state == State.RUNNING) { 114 state = State.STOPPING; 115 doStop(); 116 } 117 } catch (Throwable shutdownFailure) { 118 // put the exception in the future, the user can get it via Future.get() 119 notifyFailed(shutdownFailure); 120 } finally { 121 lock.unlock(); 122 } 123 124 return shutdown; 125 } 126 127 public State startAndWait() { 128 try { 129 return start().get(); 130 } catch (InterruptedException e) { 131 Thread.currentThread().interrupt(); 132 throw new RuntimeException(e); 133 } catch (ExecutionException e) { 134 throw Throwables.propagate(e.getCause()); 135 } 136 } 137 138 public State stopAndWait() { 139 try { 140 return stop().get(); 141 } catch (ExecutionException e) { 142 throw Throwables.propagate(e.getCause()); 143 } catch (InterruptedException e) { 144 Thread.currentThread().interrupt(); 145 throw new RuntimeException(e); 146 } 147 } 148 149 /** 150 * Implementing classes should invoke this method once their service has 151 * started. It will cause the service to transition from {@link 152 * State#STARTING} to {@link State#RUNNING}. 153 * 154 * @throws IllegalStateException if the service is not 155 * {@link State#STARTING}. 156 */ 157 protected final void notifyStarted() { 158 lock.lock(); 159 try { 160 if (state != State.STARTING) { 161 IllegalStateException failure = new IllegalStateException( 162 "Cannot notifyStarted() when the service is " + state); 163 notifyFailed(failure); 164 throw failure; 165 } 166 167 state = State.RUNNING; 168 if (shutdownWhenStartupFinishes) { 169 stop(); 170 } else { 171 startup.transitionSucceeded(State.RUNNING); 172 } 173 } finally { 174 lock.unlock(); 175 } 176 } 177 178 /** 179 * Implementing classes should invoke this method once their service has 180 * stopped. It will cause the service to transition from {@link 181 * State#STOPPING} to {@link State#TERMINATED}. 182 * 183 * @throws IllegalStateException if the service is neither {@link 184 * State#STOPPING} nor {@link State#RUNNING}. 185 */ 186 protected final void notifyStopped() { 187 lock.lock(); 188 try { 189 if (state != State.STOPPING && state != State.RUNNING) { 190 IllegalStateException failure = new IllegalStateException( 191 "Cannot notifyStopped() when the service is " + state); 192 notifyFailed(failure); 193 throw failure; 194 } 195 196 state = State.TERMINATED; 197 shutdown.transitionSucceeded(State.TERMINATED); 198 } finally { 199 lock.unlock(); 200 } 201 } 202 203 /** 204 * Invoke this method to transition the service to the 205 * {@link State#FAILED}. The service will <b>not be stopped</b> if it 206 * is running. Invoke this method when a service has failed critically or 207 * otherwise cannot be started nor stopped. 208 */ 209 protected final void notifyFailed(Throwable cause) { 210 checkNotNull(cause); 211 212 lock.lock(); 213 try { 214 if (state == State.STARTING) { 215 startup.transitionFailed(cause); 216 shutdown.transitionFailed(new Exception( 217 "Service failed to start.", cause)); 218 } else if (state == State.STOPPING) { 219 shutdown.transitionFailed(cause); 220 } 221 222 state = State.FAILED; 223 } finally { 224 lock.unlock(); 225 } 226 } 227 228 public final boolean isRunning() { 229 return state() == State.RUNNING; 230 } 231 232 public final State state() { 233 lock.lock(); 234 try { 235 if (shutdownWhenStartupFinishes && state == State.STARTING) { 236 return State.STOPPING; 237 } else { 238 return state; 239 } 240 } finally { 241 lock.unlock(); 242 } 243 } 244 245 /** 246 * A change from one service state to another, plus the result of the change. 247 * 248 * TODO: could this be renamed to DefaultFuture, with methods 249 * like setResult(T) and setFailure(T) ? 250 */ 251 private static class Transition implements Future<State> { 252 private final CountDownLatch done = new CountDownLatch(1); 253 private State result; 254 private Throwable failureCause; 255 256 void transitionSucceeded(State result) { 257 // guarded by AbstractService.lock 258 checkState(this.result == null); 259 this.result = result; 260 done.countDown(); 261 } 262 263 void transitionFailed(Throwable cause) { 264 // guarded by AbstractService.lock 265 checkState(result == null); 266 this.result = State.FAILED; 267 this.failureCause = cause; 268 done.countDown(); 269 } 270 271 public boolean cancel(boolean mayInterruptIfRunning) { 272 return false; 273 } 274 275 public boolean isCancelled() { 276 return false; 277 } 278 279 public boolean isDone() { 280 return done.getCount() == 0; 281 } 282 283 public State get() throws InterruptedException, ExecutionException { 284 done.await(); 285 return getImmediately(); 286 } 287 288 public State get(long timeout, TimeUnit unit) 289 throws InterruptedException, ExecutionException, TimeoutException { 290 if (done.await(timeout, unit)) { 291 return getImmediately(); 292 } 293 throw new TimeoutException(); 294 } 295 296 private State getImmediately() throws ExecutionException { 297 if (result == State.FAILED) { 298 throw new ExecutionException(failureCause); 299 } else { 300 return result; 301 } 302 } 303 } 304 } 305