• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    */
doStart()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    */
doStop()84   protected abstract void doStop();
85 
start()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 
stop()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 
startAndWait()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 
stopAndWait()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    */
notifyStarted()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    */
notifyStopped()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    */
notifyFailed(Throwable cause)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 
isRunning()228   public final boolean isRunning() {
229     return state() == State.RUNNING;
230   }
231 
state()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 
transitionSucceeded(State result)256     void transitionSucceeded(State result) {
257       // guarded by AbstractService.lock
258       checkState(this.result == null);
259       this.result = result;
260       done.countDown();
261     }
262 
transitionFailed(Throwable cause)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 
cancel(boolean mayInterruptIfRunning)271     public boolean cancel(boolean mayInterruptIfRunning) {
272       return false;
273     }
274 
isCancelled()275     public boolean isCancelled() {
276       return false;
277     }
278 
isDone()279     public boolean isDone() {
280       return done.getCount() == 0;
281     }
282 
get()283     public State get() throws InterruptedException, ExecutionException {
284       done.await();
285       return getImmediately();
286     }
287 
get(long timeout, TimeUnit unit)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 
getImmediately()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