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    }