001/*
002 * Copyright (C) 2009 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.util.concurrent;
016
017import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException;
018
019import com.google.common.annotations.GwtIncompatible;
020import com.google.common.annotations.J2ktIncompatible;
021import com.google.errorprone.annotations.CanIgnoreReturnValue;
022import java.time.Duration;
023import java.util.concurrent.Executor;
024import java.util.concurrent.TimeUnit;
025import java.util.concurrent.TimeoutException;
026
027/**
028 * Base class for services that can implement {@link #startUp}, {@link #run} and {@link #shutDown}
029 * methods. This class uses a single thread to execute the service; consider {@link AbstractService}
030 * if you would like to manage any threading manually.
031 *
032 * @author Jesse Wilson
033 * @since 1.0
034 */
035@GwtIncompatible
036@J2ktIncompatible
037public abstract class AbstractExecutionThreadService implements Service {
038  /* use AbstractService for state management */
039  private final Service delegate =
040      new AbstractService() {
041        @Override
042        protected final void doStart() {
043          Executor executor = MoreExecutors.renamingDecorator(executor(), () -> serviceName());
044          executor.execute(
045              () -> {
046                try {
047                  startUp();
048                  notifyStarted();
049                  // If stopAsync() is called while starting we may be in the STOPPING state in
050                  // which case we should skip right down to shutdown.
051                  if (isRunning()) {
052                    try {
053                      AbstractExecutionThreadService.this.run();
054                    } catch (Throwable t) {
055                      restoreInterruptIfIsInterruptedException(t);
056                      try {
057                        shutDown();
058                      } catch (Exception ignored) {
059                        restoreInterruptIfIsInterruptedException(ignored);
060                        t.addSuppressed(ignored);
061                      }
062                      notifyFailed(t);
063                      return;
064                    }
065                  }
066
067                  shutDown();
068                  notifyStopped();
069                } catch (Throwable t) {
070                  restoreInterruptIfIsInterruptedException(t);
071                  notifyFailed(t);
072                }
073              });
074        }
075
076        @Override
077        protected void doStop() {
078          triggerShutdown();
079        }
080
081        @Override
082        public String toString() {
083          return AbstractExecutionThreadService.this.toString();
084        }
085      };
086
087  /** Constructor for use by subclasses. */
088  protected AbstractExecutionThreadService() {}
089
090  /**
091   * Start the service. This method is invoked on the execution thread.
092   *
093   * <p>By default this method does nothing.
094   */
095  protected void startUp() throws Exception {}
096
097  /**
098   * Run the service. This method is invoked on the execution thread. Implementations must respond
099   * to stop requests. You could poll for lifecycle changes in a work loop:
100   *
101   * <pre>
102   *   public void run() {
103   *     while ({@link #isRunning()}) {
104   *       // perform a unit of work
105   *     }
106   *   }
107   * </pre>
108   *
109   * <p>...or you could respond to stop requests by implementing {@link #triggerShutdown()}, which
110   * should cause {@link #run()} to return.
111   */
112  protected abstract void run() throws Exception;
113
114  /**
115   * Stop the service. This method is invoked on the execution thread.
116   *
117   * <p>By default this method does nothing.
118   */
119  // TODO: consider supporting a TearDownTestCase-like API
120  protected void shutDown() throws Exception {}
121
122  /**
123   * Invoked to request the service to stop.
124   *
125   * <p>By default this method does nothing.
126   *
127   * <p>Currently, this method is invoked while holding a lock. If an implementation of this method
128   * blocks, it can prevent this service from changing state. If you need to performing a blocking
129   * operation in order to trigger shutdown, consider instead registering a listener and
130   * implementing {@code stopping}. Note, however, that {@code stopping} does not run at exactly the
131   * same times as {@code triggerShutdown}.
132   */
133  protected void triggerShutdown() {}
134
135  /**
136   * Returns the {@link Executor} that will be used to run this service. Subclasses may override
137   * this method to use a custom {@link Executor}, which may configure its worker thread with a
138   * specific name, thread group or priority. The returned executor's {@link
139   * Executor#execute(Runnable) execute()} method is called when this service is started, and should
140   * return promptly.
141   *
142   * <p>The default implementation returns a new {@link Executor} that sets the name of its threads
143   * to the string returned by {@link #serviceName}
144   */
145  protected Executor executor() {
146    return command -> MoreExecutors.newThread(serviceName(), command).start();
147  }
148
149  @Override
150  public String toString() {
151    return serviceName() + " [" + state() + "]";
152  }
153
154  @Override
155  public final boolean isRunning() {
156    return delegate.isRunning();
157  }
158
159  @Override
160  public final State state() {
161    return delegate.state();
162  }
163
164  /** @since 13.0 */
165  @Override
166  public final void addListener(Listener listener, Executor executor) {
167    delegate.addListener(listener, executor);
168  }
169
170  /** @since 14.0 */
171  @Override
172  public final Throwable failureCause() {
173    return delegate.failureCause();
174  }
175
176  /** @since 15.0 */
177  @CanIgnoreReturnValue
178  @Override
179  public final Service startAsync() {
180    delegate.startAsync();
181    return this;
182  }
183
184  /** @since 15.0 */
185  @CanIgnoreReturnValue
186  @Override
187  public final Service stopAsync() {
188    delegate.stopAsync();
189    return this;
190  }
191
192  /** @since 15.0 */
193  @Override
194  public final void awaitRunning() {
195    delegate.awaitRunning();
196  }
197
198  /** @since 28.0 */
199  @Override
200  public final void awaitRunning(Duration timeout) throws TimeoutException {
201    Service.super.awaitRunning(timeout);
202  }
203
204  /** @since 15.0 */
205  @Override
206  public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException {
207    delegate.awaitRunning(timeout, unit);
208  }
209
210  /** @since 15.0 */
211  @Override
212  public final void awaitTerminated() {
213    delegate.awaitTerminated();
214  }
215
216  /** @since 28.0 */
217  @Override
218  public final void awaitTerminated(Duration timeout) throws TimeoutException {
219    Service.super.awaitTerminated(timeout);
220  }
221
222  /** @since 15.0 */
223  @Override
224  public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException {
225    delegate.awaitTerminated(timeout, unit);
226  }
227
228  /**
229   * Returns the name of this service. {@link AbstractExecutionThreadService} may include the name
230   * in debugging output.
231   *
232   * <p>Subclasses may override this method.
233   *
234   * @since 14.0 (present in 10.0 as getServiceName)
235   */
236  protected String serviceName() {
237    return getClass().getSimpleName();
238  }
239}