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