• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.annotations.Beta;
22 import com.google.common.util.concurrent.Service.State; // javadoc needs this
23 
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27 import java.util.concurrent.locks.ReentrantLock;
28 
29 /**
30  * Base class for implementing services that can handle {@link #doStart} and
31  * {@link #doStop} requests, responding to them with {@link #notifyStarted()}
32  * and {@link #notifyStopped()} callbacks. Its subclasses must manage threads
33  * manually; consider {@link AbstractExecutionThreadService} if you need only a
34  * single execution thread.
35  *
36  * @author Jesse Wilson
37  * @since 1.0
38  */
39 @Beta
40 public abstract class AbstractService implements Service {
41 
42   private final ReentrantLock lock = new ReentrantLock();
43 
44   private final Transition startup = new Transition();
45   private final Transition shutdown = new Transition();
46 
47   /**
48    * The internal state, which equals external state unless
49    * shutdownWhenStartupFinishes is true. Guarded by {@code lock}.
50    */
51   private State state = State.NEW;
52 
53   /**
54    * If true, the user requested a shutdown while the service was still starting
55    * up. Guarded by {@code lock}.
56    */
57   private boolean shutdownWhenStartupFinishes = false;
58 
59   /**
60    * This method is called by {@link #start} to initiate service startup. The
61    * invocation of this method should cause a call to {@link #notifyStarted()},
62    * either during this method's run, or after it has returned. If startup
63    * fails, the invocation should cause a call to {@link
64    * #notifyFailed(Throwable)} instead.
65    *
66    * <p>This method should return promptly; prefer to do work on a different
67    * thread where it is convenient. It is invoked exactly once on service
68    * startup, even when {@link #start} is called multiple times.
69    */
doStart()70   protected abstract void doStart();
71 
72   /**
73    * This method should be used to initiate service shutdown. The invocation
74    * of this method should cause a call to {@link #notifyStopped()}, either
75    * during this method's run, or after it has returned. If shutdown fails, the
76    * invocation should cause a call to {@link #notifyFailed(Throwable)} instead.
77    *
78    * <p>This method should return promptly; prefer to do work on a different
79    * thread where it is convenient. It is invoked exactly once on service
80    * shutdown, even when {@link #stop} is called multiple times.
81    */
doStop()82   protected abstract void doStop();
83 
84   @Override
start()85   public final ListenableFuture<State> start() {
86     lock.lock();
87     try {
88       if (state == State.NEW) {
89         state = State.STARTING;
90         doStart();
91       }
92     } catch (Throwable startupFailure) {
93       // put the exception in the future, the user can get it via Future.get()
94       notifyFailed(startupFailure);
95     } finally {
96       lock.unlock();
97     }
98 
99     return startup;
100   }
101 
102   @Override
stop()103   public final ListenableFuture<State> stop() {
104     lock.lock();
105     try {
106       if (state == State.NEW) {
107         state = State.TERMINATED;
108         startup.set(State.TERMINATED);
109         shutdown.set(State.TERMINATED);
110       } else if (state == State.STARTING) {
111         shutdownWhenStartupFinishes = true;
112         startup.set(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   @Override
startAndWait()128   public State startAndWait() {
129     return Futures.getUnchecked(start());
130   }
131 
132   @Override
stopAndWait()133   public State stopAndWait() {
134     return Futures.getUnchecked(stop());
135   }
136 
137   /**
138    * Implementing classes should invoke this method once their service has
139    * started. It will cause the service to transition from {@link
140    * State#STARTING} to {@link State#RUNNING}.
141    *
142    * @throws IllegalStateException if the service is not
143    *     {@link State#STARTING}.
144    */
notifyStarted()145   protected final void notifyStarted() {
146     lock.lock();
147     try {
148       if (state != State.STARTING) {
149         IllegalStateException failure = new IllegalStateException(
150             "Cannot notifyStarted() when the service is " + state);
151         notifyFailed(failure);
152         throw failure;
153       }
154 
155       state = State.RUNNING;
156       if (shutdownWhenStartupFinishes) {
157         stop();
158       } else {
159         startup.set(State.RUNNING);
160       }
161     } finally {
162       lock.unlock();
163     }
164   }
165 
166   /**
167    * Implementing classes should invoke this method once their service has
168    * stopped. It will cause the service to transition from {@link
169    * State#STOPPING} to {@link State#TERMINATED}.
170    *
171    * @throws IllegalStateException if the service is neither {@link
172    *     State#STOPPING} nor {@link State#RUNNING}.
173    */
notifyStopped()174   protected final void notifyStopped() {
175     lock.lock();
176     try {
177       if (state != State.STOPPING && state != State.RUNNING) {
178         IllegalStateException failure = new IllegalStateException(
179             "Cannot notifyStopped() when the service is " + state);
180         notifyFailed(failure);
181         throw failure;
182       }
183 
184       state = State.TERMINATED;
185       shutdown.set(State.TERMINATED);
186     } finally {
187       lock.unlock();
188     }
189   }
190 
191   /**
192    * Invoke this method to transition the service to the
193    * {@link State#FAILED}. The service will <b>not be stopped</b> if it
194    * is running. Invoke this method when a service has failed critically or
195    * otherwise cannot be started nor stopped.
196    */
notifyFailed(Throwable cause)197   protected final void notifyFailed(Throwable cause) {
198     checkNotNull(cause);
199 
200     lock.lock();
201     try {
202       if (state == State.STARTING) {
203         startup.setException(cause);
204         shutdown.setException(new Exception(
205             "Service failed to start.", cause));
206       } else if (state == State.STOPPING) {
207         shutdown.setException(cause);
208       } else if (state == State.RUNNING) {
209         shutdown.setException(new Exception("Service failed while running", cause));
210       } else if (state == State.NEW || state == State.TERMINATED) {
211         throw new IllegalStateException("Failed while in state:" + state, cause);
212       }
213       state = State.FAILED;
214     } finally {
215       lock.unlock();
216     }
217   }
218 
219   @Override
isRunning()220   public final boolean isRunning() {
221     return state() == State.RUNNING;
222   }
223 
224   @Override
state()225   public final State state() {
226     lock.lock();
227     try {
228       if (shutdownWhenStartupFinishes && state == State.STARTING) {
229         return State.STOPPING;
230       } else {
231         return state;
232       }
233     } finally {
234       lock.unlock();
235     }
236   }
237 
toString()238   @Override public String toString() {
239     return getClass().getSimpleName() + " [" + state() + "]";
240   }
241 
242   /**
243    * A change from one service state to another, plus the result of the change.
244    */
245   private class Transition extends AbstractFuture<State> {
246     @Override
get(long timeout, TimeUnit unit)247     public State get(long timeout, TimeUnit unit)
248         throws InterruptedException, TimeoutException, ExecutionException {
249       try {
250         return super.get(timeout, unit);
251       } catch (TimeoutException e) {
252         throw new TimeoutException(AbstractService.this.toString());
253       }
254     }
255   }
256 }
257