001 /* 002 * Copyright (C) 2009 Google Inc. 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 com.google.common.base.Preconditions.checkNotNull; 020 import static com.google.common.base.Preconditions.checkState; 021 022 import com.google.common.annotations.Beta; 023 import com.google.common.base.Service; 024 import com.google.common.base.Service.State; // javadoc needs this 025 import com.google.common.base.Throwables; 026 027 import java.util.concurrent.CountDownLatch; 028 import java.util.concurrent.ExecutionException; 029 import java.util.concurrent.Future; 030 import java.util.concurrent.TimeUnit; 031 import java.util.concurrent.TimeoutException; 032 import java.util.concurrent.locks.ReentrantLock; 033 034 /** 035 * Base class for implementing services that can handle {@link #doStart} and 036 * {@link #doStop} requests, responding to them with {@link #notifyStarted()} 037 * and {@link #notifyStopped()} callbacks. Its subclasses must manage threads 038 * manually; consider {@link AbstractExecutionThreadService} if you need only a 039 * single execution thread. 040 * 041 * @author Jesse Wilson 042 * @since 1 043 */ 044 @Beta 045 public abstract class AbstractService implements Service { 046 047 private final ReentrantLock lock = new ReentrantLock(); 048 049 private final Transition startup = new Transition(); 050 private final Transition shutdown = new Transition(); 051 052 /** 053 * The internal state, which equals external state unless 054 * shutdownWhenStartupFinishes is true. Guarded by {@code lock}. 055 */ 056 private State state = State.NEW; 057 058 /** 059 * If true, the user requested a shutdown while the service was still starting 060 * up. Guarded by {@code lock}. 061 */ 062 private boolean shutdownWhenStartupFinishes = false; 063 064 /** 065 * This method is called by {@link #start} to initiate service startup. The 066 * invocation of this method should cause a call to {@link #notifyStarted()}, 067 * either during this method's run, or after it has returned. If startup 068 * fails, the invocation should cause a call to {@link 069 * #notifyFailed(Throwable)} instead. 070 * 071 * <p>This method should return promptly; prefer to do work on a different 072 * thread where it is convenient. It is invoked exactly once on service 073 * startup, even when {@link #start} is called multiple times. 074 */ 075 protected abstract void doStart(); 076 077 /** 078 * This method should be used to initiate service shutdown. The invocation 079 * of this method should cause a call to {@link #notifyStopped()}, either 080 * during this method's run, or after it has returned. If shutdown fails, the 081 * invocation should cause a call to {@link #notifyFailed(Throwable)} instead. 082 * 083 * <p>This method should return promptly; prefer to do work on a different 084 * thread where it is convenient. It is invoked exactly once on service 085 * shutdown, even when {@link #stop} is called multiple times. 086 */ 087 protected abstract void doStop(); 088 089 public final Future<State> start() { 090 lock.lock(); 091 try { 092 if (state == State.NEW) { 093 state = State.STARTING; 094 doStart(); 095 } 096 } catch (Throwable startupFailure) { 097 // put the exception in the future, the user can get it via Future.get() 098 notifyFailed(startupFailure); 099 } finally { 100 lock.unlock(); 101 } 102 103 return startup; 104 } 105 106 public final Future<State> stop() { 107 lock.lock(); 108 try { 109 if (state == State.NEW) { 110 state = State.TERMINATED; 111 startup.transitionSucceeded(State.TERMINATED); 112 shutdown.transitionSucceeded(State.TERMINATED); 113 } else if (state == State.STARTING) { 114 shutdownWhenStartupFinishes = true; 115 startup.transitionSucceeded(State.STOPPING); 116 } else if (state == State.RUNNING) { 117 state = State.STOPPING; 118 doStop(); 119 } 120 } catch (Throwable shutdownFailure) { 121 // put the exception in the future, the user can get it via Future.get() 122 notifyFailed(shutdownFailure); 123 } finally { 124 lock.unlock(); 125 } 126 127 return shutdown; 128 } 129 130 public State startAndWait() { 131 try { 132 return start().get(); 133 } catch (InterruptedException e) { 134 Thread.currentThread().interrupt(); 135 throw new RuntimeException(e); 136 } catch (ExecutionException e) { 137 throw Throwables.propagate(e.getCause()); 138 } 139 } 140 141 public State stopAndWait() { 142 try { 143 return stop().get(); 144 } catch (ExecutionException e) { 145 throw Throwables.propagate(e.getCause()); 146 } catch (InterruptedException e) { 147 Thread.currentThread().interrupt(); 148 throw new RuntimeException(e); 149 } 150 } 151 152 /** 153 * Implementing classes should invoke this method once their service has 154 * started. It will cause the service to transition from {@link 155 * State#STARTING} to {@link State#RUNNING}. 156 * 157 * @throws IllegalStateException if the service is not 158 * {@link State#STARTING}. 159 */ 160 protected final void notifyStarted() { 161 lock.lock(); 162 try { 163 if (state != State.STARTING) { 164 IllegalStateException failure = new IllegalStateException( 165 "Cannot notifyStarted() when the service is " + state); 166 notifyFailed(failure); 167 throw failure; 168 } 169 170 state = State.RUNNING; 171 if (shutdownWhenStartupFinishes) { 172 stop(); 173 } else { 174 startup.transitionSucceeded(State.RUNNING); 175 } 176 } finally { 177 lock.unlock(); 178 } 179 } 180 181 /** 182 * Implementing classes should invoke this method once their service has 183 * stopped. It will cause the service to transition from {@link 184 * State#STOPPING} to {@link State#TERMINATED}. 185 * 186 * @throws IllegalStateException if the service is neither {@link 187 * State#STOPPING} nor {@link State#RUNNING}. 188 */ 189 protected final void notifyStopped() { 190 lock.lock(); 191 try { 192 if (state != State.STOPPING && state != State.RUNNING) { 193 IllegalStateException failure = new IllegalStateException( 194 "Cannot notifyStopped() when the service is " + state); 195 notifyFailed(failure); 196 throw failure; 197 } 198 199 state = State.TERMINATED; 200 shutdown.transitionSucceeded(State.TERMINATED); 201 } finally { 202 lock.unlock(); 203 } 204 } 205 206 /** 207 * Invoke this method to transition the service to the 208 * {@link State#FAILED}. The service will <b>not be stopped</b> if it 209 * is running. Invoke this method when a service has failed critically or 210 * otherwise cannot be started nor stopped. 211 */ 212 protected final void notifyFailed(Throwable cause) { 213 checkNotNull(cause); 214 215 lock.lock(); 216 try { 217 if (state == State.STARTING) { 218 startup.transitionFailed(cause); 219 shutdown.transitionFailed(new Exception( 220 "Service failed to start.", cause)); 221 } else if (state == State.STOPPING) { 222 shutdown.transitionFailed(cause); 223 } 224 225 state = State.FAILED; 226 } finally { 227 lock.unlock(); 228 } 229 } 230 231 public final boolean isRunning() { 232 return state() == State.RUNNING; 233 } 234 235 public final State state() { 236 lock.lock(); 237 try { 238 if (shutdownWhenStartupFinishes && state == State.STARTING) { 239 return State.STOPPING; 240 } else { 241 return state; 242 } 243 } finally { 244 lock.unlock(); 245 } 246 } 247 248 @Override public String toString() { 249 return getClass().getSimpleName() + " [" + state() + "]"; 250 } 251 252 /** 253 * A change from one service state to another, plus the result of the change. 254 * 255 * TODO: could this be renamed to DefaultFuture, with methods 256 * like setResult(T) and setFailure(T) ? 257 */ 258 private class Transition implements Future<State> { 259 private final CountDownLatch done = new CountDownLatch(1); 260 private State result; 261 private Throwable failureCause; 262 263 void transitionSucceeded(State result) { 264 // guarded by AbstractService.lock 265 checkState(this.result == null); 266 this.result = result; 267 done.countDown(); 268 } 269 270 void transitionFailed(Throwable cause) { 271 // guarded by AbstractService.lock 272 checkState(result == null); 273 this.result = State.FAILED; 274 this.failureCause = cause; 275 done.countDown(); 276 } 277 278 public boolean cancel(boolean mayInterruptIfRunning) { 279 return false; 280 } 281 282 public boolean isCancelled() { 283 return false; 284 } 285 286 public boolean isDone() { 287 return done.getCount() == 0; 288 } 289 290 public State get() throws InterruptedException, ExecutionException { 291 done.await(); 292 return getImmediately(); 293 } 294 295 public State get(long timeout, TimeUnit unit) 296 throws InterruptedException, ExecutionException, TimeoutException { 297 if (done.await(timeout, unit)) { 298 return getImmediately(); 299 } 300 throw new TimeoutException(AbstractService.this.toString()); 301 } 302 303 private State getImmediately() throws ExecutionException { 304 if (result == State.FAILED) { 305 throw new ExecutionException(failureCause); 306 } else { 307 return result; 308 } 309 } 310 } 311 }