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
037@ElementTypesAreNonnullByDefault
038public abstract class AbstractExecutionThreadService implements Service {
039  /* use AbstractService for state management */
040  private final Service delegate =
041      new AbstractService() {
042        @Override
043        protected final void doStart() {
044          Executor executor = MoreExecutors.renamingDecorator(executor(), () -> serviceName());
045          executor.execute(
046              () -> {
047                try {
048                  startUp();
049                  notifyStarted();
050                  // If stopAsync() is called while starting we may be in the STOPPING state in
051                  // which case we should skip right down to shutdown.
052                  if (isRunning()) {
053                    try {
054                      AbstractExecutionThreadService.this.run();
055                    } catch (Throwable t) {
056                      restoreInterruptIfIsInterruptedException(t);
057                      try {
058                        shutDown();
059                      } catch (Exception ignored) {
060                        restoreInterruptIfIsInterruptedException(ignored);
061                        t.addSuppressed(ignored);
062                      }
063                      notifyFailed(t);
064                      return;
065                    }
066                  }
067
068                  shutDown();
069                  notifyStopped();
070                } catch (Throwable t) {
071                  restoreInterruptIfIsInterruptedException(t);
072                  notifyFailed(t);
073                }
074              });
075        }
076
077        @Override
078        protected void doStop() {
079          triggerShutdown();
080        }
081
082        @Override
083        public String toString() {
084          return AbstractExecutionThreadService.this.toString();
085        }
086      };
087
088  /** Constructor for use by subclasses. */
089  protected AbstractExecutionThreadService() {}
090
091  /**
092   * Start the service. This method is invoked on the execution thread.
093   *
094   * <p>By default this method does nothing.
095   */
096  protected void startUp() throws Exception {}
097
098  /**
099   * Run the service. This method is invoked on the execution thread. Implementations must respond
100   * to stop requests. You could poll for lifecycle changes in a work loop:
101   *
102   * <pre>
103   *   public void run() {
104   *     while ({@link #isRunning()}) {
105   *       // perform a unit of work
106   *     }
107   *   }
108   * </pre>
109   *
110   * <p>...or you could respond to stop requests by implementing {@link #triggerShutdown()}, which
111   * should cause {@link #run()} to return.
112   */
113  protected abstract void run() throws Exception;
114
115  /**
116   * Stop the service. This method is invoked on the execution thread.
117   *
118   * <p>By default this method does nothing.
119   */
120  // TODO: consider supporting a TearDownTestCase-like API
121  protected void shutDown() throws Exception {}
122
123  /**
124   * Invoked to request the service to stop.
125   *
126   * <p>By default this method does nothing.
127   *
128   * <p>Currently, this method is invoked while holding a lock. If an implementation of this method
129   * blocks, it can prevent this service from changing state. If you need to performing a blocking
130   * operation in order to trigger shutdown, consider instead registering a listener and
131   * implementing {@code stopping}. Note, however, that {@code stopping} does not run at exactly the
132   * same times as {@code triggerShutdown}.
133   */
134  protected void triggerShutdown() {}
135
136  /**
137   * Returns the {@link Executor} that will be used to run this service. Subclasses may override
138   * this method to use a custom {@link Executor}, which may configure its worker thread with a
139   * specific name, thread group or priority. The returned executor's {@link
140   * Executor#execute(Runnable) execute()} method is called when this service is started, and should
141   * return promptly.
142   *
143   * <p>The default implementation returns a new {@link Executor} that sets the name of its threads
144   * to the string returned by {@link #serviceName}
145   */
146  protected Executor executor() {
147    return command -> MoreExecutors.newThread(serviceName(), command).start();
148  }
149
150  @Override
151  public String toString() {
152    return serviceName() + " [" + state() + "]";
153  }
154
155  @Override
156  public final boolean isRunning() {
157    return delegate.isRunning();
158  }
159
160  @Override
161  public final State state() {
162    return delegate.state();
163  }
164
165  /** @since 13.0 */
166  @Override
167  public final void addListener(Listener listener, Executor executor) {
168    delegate.addListener(listener, executor);
169  }
170
171  /** @since 14.0 */
172  @Override
173  public final Throwable failureCause() {
174    return delegate.failureCause();
175  }
176
177  /** @since 15.0 */
178  @CanIgnoreReturnValue
179  @Override
180  public final Service startAsync() {
181    delegate.startAsync();
182    return this;
183  }
184
185  /** @since 15.0 */
186  @CanIgnoreReturnValue
187  @Override
188  public final Service stopAsync() {
189    delegate.stopAsync();
190    return this;
191  }
192
193  /** @since 15.0 */
194  @Override
195  public final void awaitRunning() {
196    delegate.awaitRunning();
197  }
198
199  /** @since 28.0 */
200  @Override
201  public final void awaitRunning(Duration timeout) throws TimeoutException {
202    Service.super.awaitRunning(timeout);
203  }
204
205  /** @since 15.0 */
206  @Override
207  public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException {
208    delegate.awaitRunning(timeout, unit);
209  }
210
211  /** @since 15.0 */
212  @Override
213  public final void awaitTerminated() {
214    delegate.awaitTerminated();
215  }
216
217  /** @since 28.0 */
218  @Override
219  public final void awaitTerminated(Duration timeout) throws TimeoutException {
220    Service.super.awaitTerminated(timeout);
221  }
222
223  /** @since 15.0 */
224  @Override
225  public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException {
226    delegate.awaitTerminated(timeout, unit);
227  }
228
229  /**
230   * Returns the name of this service. {@link AbstractExecutionThreadService} may include the name
231   * in debugging output.
232   *
233   * <p>Subclasses may override this method.
234   *
235   * @since 14.0 (present in 10.0 as getServiceName)
236   */
237  protected String serviceName() {
238    return getClass().getSimpleName();
239  }
240}