001 /* 002 * Copyright (C) 2011 The Guava Authors 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 com.google.common.annotations.Beta; 020 import com.google.common.base.Preconditions; 021 import com.google.common.base.Throwables; 022 023 import java.util.concurrent.Callable; 024 import java.util.concurrent.Executors; 025 import java.util.concurrent.Future; 026 import java.util.concurrent.ScheduledExecutorService; 027 import java.util.concurrent.TimeUnit; 028 import java.util.concurrent.locks.ReentrantLock; 029 import java.util.logging.Level; 030 import java.util.logging.Logger; 031 032 import javax.annotation.concurrent.GuardedBy; 033 034 /** 035 * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in 036 * the "running" state need to perform a periodic task. Subclasses can implement {@link #startUp}, 037 * {@link #shutDown} and also a {@link #runOneIteration} method that will be executed periodically. 038 * 039 * <p>This class uses the {@link ScheduledExecutorService} returned from {@link #executor} to run 040 * the {@link #startUp} and {@link #shutDown} methods and also uses that service to schedule the 041 * {@link #runOneIteration} that will be executed periodically as specified by its 042 * {@link Scheduler}. When this service is asked to stop via {@link #stop} or {@link #stopAndWait}, 043 * it will cancel the periodic task (but not interrupt it) and wait for it to stop before running 044 * the {@link #shutDown} method. 045 * 046 * <h3>Usage Example</h3> 047 * 048 * Here is a sketch of a service which crawls a website and uses the scheduling capabilities to 049 * rate limit itself. <pre> {@code 050 * class CrawlingService extends AbstractScheduledService { 051 * private Set<Uri> visited; 052 * private Queue<Uri> toCrawl; 053 * protected void startUp() throws Exception { 054 * toCrawl = readStartingUris(); 055 * } 056 * 057 * protected void runOneIteration() throws Exception { 058 * Uri uri = toCrawl.remove(); 059 * Collection<Uri> newUris = crawl(uri); 060 * visited.add(uri); 061 * for (Uri newUri : newUris) { 062 * if (!visited.contains(newUri)) { toCrawl.add(newUri); } 063 * } 064 * } 065 * 066 * protected void shutDown() throws Exception { 067 * saveUris(toCrawl); 068 * } 069 * 070 * protected Schedule schedule() { 071 * return newFixedRateSchedule(0, 1, TimeUnit.SECONDS); 072 * } 073 * }}</pre> 074 * 075 * This class uses the lifecycle methods to read in a list of starting URIs and save the set of 076 * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to 077 * rate limit the number of queries we perform. 078 * 079 * @author Luke Sandberg 080 * @since 11.0 081 */ 082 @Beta 083 public abstract class AbstractScheduledService implements Service { 084 private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); 085 086 /** 087 * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its 088 * task. 089 * 090 * <p>Consider using the {@link #newFixedDelaySchedule} and {@link #newFixedRateSchedule} factory 091 * methods, these provide {@link Scheduler} instances for the common use case of running the 092 * service with a fixed schedule. If more flexibility is needed then consider subclassing the 093 * {@link CustomScheduler} abstract class in preference to creating your own {@link Scheduler} 094 * implementation. 095 * 096 * @author Luke Sandberg 097 * @since 11.0 098 */ 099 public abstract static class Scheduler { 100 /** 101 * Returns a {@link Scheduler} that schedules the task using the 102 * {@link ScheduledExecutorService#scheduleWithFixedDelay} method. 103 * 104 * @param initialDelay the time to delay first execution 105 * @param delay the delay between the termination of one execution and the commencement of the 106 * next 107 * @param unit the time unit of the initialDelay and delay parameters 108 */ 109 public static Scheduler newFixedDelaySchedule(final long initialDelay, final long delay, 110 final TimeUnit unit) { 111 return new Scheduler() { 112 @Override 113 public Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 114 Runnable task) { 115 return executor.scheduleWithFixedDelay(task, initialDelay, delay, unit); 116 } 117 }; 118 } 119 120 /** 121 * Returns a {@link Scheduler} that schedules the task using the 122 * {@link ScheduledExecutorService#scheduleAtFixedRate} method. 123 * 124 * @param initialDelay the time to delay first execution 125 * @param period the period between successive executions of the task 126 * @param unit the time unit of the initialDelay and period parameters 127 */ 128 public static Scheduler newFixedRateSchedule(final long initialDelay, final long period, 129 final TimeUnit unit) { 130 return new Scheduler() { 131 @Override 132 public Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 133 Runnable task) { 134 return executor.scheduleAtFixedRate(task, initialDelay, period, unit); 135 } 136 }; 137 } 138 139 /** Schedules the task to run on the provided executor on behalf of the service. */ 140 abstract Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 141 Runnable runnable); 142 143 private Scheduler() {} 144 } 145 146 /* use AbstractService for state management */ 147 private final AbstractService delegate = new AbstractService() { 148 149 // A handle to the running task so that we can stop it when a shutdown has been requested. 150 // These two fields are volatile because their values will be accessed from multiple threads. 151 private volatile Future<?> runningTask; 152 private volatile ScheduledExecutorService executorService; 153 154 // This lock protects the task so we can ensure that none of the template methods (startUp, 155 // shutDown or runOneIteration) run concurrently with one another. 156 private final ReentrantLock lock = new ReentrantLock(); 157 158 private final Runnable task = new Runnable() { 159 @Override public void run() { 160 lock.lock(); 161 try { 162 AbstractScheduledService.this.runOneIteration(); 163 } catch (Throwable t) { 164 try { 165 shutDown(); 166 } catch (Exception ignored) { 167 logger.log(Level.WARNING, 168 "Error while attempting to shut down the service after failure.", ignored); 169 } 170 notifyFailed(t); 171 throw Throwables.propagate(t); 172 } finally { 173 lock.unlock(); 174 } 175 } 176 }; 177 178 @Override protected final void doStart() { 179 executorService = executor(); 180 executorService.execute(new Runnable() { 181 @Override public void run() { 182 lock.lock(); 183 try { 184 startUp(); 185 runningTask = scheduler().schedule(delegate, executorService, task); 186 notifyStarted(); 187 } catch (Throwable t) { 188 notifyFailed(t); 189 throw Throwables.propagate(t); 190 } finally { 191 lock.unlock(); 192 } 193 } 194 }); 195 } 196 197 @Override protected final void doStop() { 198 runningTask.cancel(false); 199 executorService.execute(new Runnable() { 200 @Override public void run() { 201 try { 202 lock.lock(); 203 try { 204 if (state() != State.STOPPING) { 205 // This means that the state has changed since we were scheduled. This implies that 206 // an execution of runOneIteration has thrown an exception and we have transitioned 207 // to a failed state, also this means that shutDown has already been called, so we 208 // do not want to call it again. 209 return; 210 } 211 shutDown(); 212 } finally { 213 lock.unlock(); 214 } 215 notifyStopped(); 216 } catch (Throwable t) { 217 notifyFailed(t); 218 throw Throwables.propagate(t); 219 } 220 } 221 }); 222 } 223 }; 224 225 /** 226 * Run one iteration of the scheduled task. If any invocation of this method throws an exception, 227 * the service will transition to the {@link Service.State#FAILED} state and this method will no 228 * longer be called. 229 */ 230 protected abstract void runOneIteration() throws Exception; 231 232 /** Start the service. */ 233 protected abstract void startUp() throws Exception; 234 235 /** Stop the service. This is guaranteed not to run concurrently with {@link #runOneIteration}. */ 236 protected abstract void shutDown() throws Exception; 237 238 /** 239 * Returns the {@link Scheduler} object used to configure this service. This method will only be 240 * called once. 241 */ 242 protected abstract Scheduler scheduler(); 243 244 /** 245 * Returns the {@link ScheduledExecutorService} that will be used to execute the {@link #startUp}, 246 * {@link #runOneIteration} and {@link #shutDown} methods. The executor will not be 247 * {@link ScheduledExecutorService#shutdown} when this service stops. Subclasses may override this 248 * method to use a custom {@link ScheduledExecutorService} instance. 249 * 250 * <p>By default this returns a new {@link ScheduledExecutorService} with a single thread thread 251 * pool. This method will only be called once. 252 */ 253 protected ScheduledExecutorService executor() { 254 return Executors.newSingleThreadScheduledExecutor(); 255 } 256 257 @Override public String toString() { 258 return getClass().getSimpleName() + " [" + state() + "]"; 259 } 260 261 // We override instead of using ForwardingService so that these can be final. 262 263 @Override public final ListenableFuture<State> start() { 264 return delegate.start(); 265 } 266 267 @Override public final State startAndWait() { 268 return delegate.startAndWait(); 269 } 270 271 @Override public final boolean isRunning() { 272 return delegate.isRunning(); 273 } 274 275 @Override public final State state() { 276 return delegate.state(); 277 } 278 279 @Override public final ListenableFuture<State> stop() { 280 return delegate.stop(); 281 } 282 283 @Override public final State stopAndWait() { 284 return delegate.stopAndWait(); 285 } 286 287 /** 288 * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to 289 * use a dynamically changing schedule. After every execution of the task, assuming it hasn't 290 * been cancelled, the {@link #getNextSchedule} method will be called. 291 * 292 * @author Luke Sandberg 293 * @since 11.0 294 */ 295 @Beta 296 public abstract static class CustomScheduler extends Scheduler { 297 298 /** 299 * A callable class that can reschedule itself using a {@link CustomScheduler}. 300 */ 301 private class ReschedulableCallable extends ForwardingFuture<Void> implements Callable<Void> { 302 303 /** The underlying task. */ 304 private final Runnable wrappedRunnable; 305 306 /** The executor on which this Callable will be scheduled. */ 307 private final ScheduledExecutorService executor; 308 309 /** 310 * The service that is managing this callable. This is used so that failure can be 311 * reported properly. 312 */ 313 private final AbstractService service; 314 315 /** 316 * This lock is used to ensure safe and correct cancellation, it ensures that a new task is 317 * not scheduled while a cancel is ongoing. Also it protects the currentFuture variable to 318 * ensure that it is assigned atomically with being scheduled. 319 */ 320 private final ReentrantLock lock = new ReentrantLock(); 321 322 /** The future that represents the next execution of this task.*/ 323 @GuardedBy("lock") 324 private Future<Void> currentFuture; 325 326 ReschedulableCallable(AbstractService service, ScheduledExecutorService executor, 327 Runnable runnable) { 328 this.wrappedRunnable = runnable; 329 this.executor = executor; 330 this.service = service; 331 } 332 333 @Override 334 public Void call() throws Exception { 335 wrappedRunnable.run(); 336 reschedule(); 337 return null; 338 } 339 340 /** 341 * Atomically reschedules this task and assigns the new future to {@link #currentFuture}. 342 */ 343 public void reschedule() { 344 // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that 345 // cancel calls cancel on the correct future. 2. we want to make sure that the assignment 346 // to currentFuture doesn't race with itself so that currentFuture is assigned in the 347 // correct order. 348 lock.lock(); 349 try { 350 if (currentFuture == null || !currentFuture.isCancelled()) { 351 final Schedule schedule = CustomScheduler.this.getNextSchedule(); 352 currentFuture = executor.schedule(this, schedule.delay, schedule.unit); 353 } 354 } catch (Throwable e) { 355 // If an exception is thrown by the subclass then we need to make sure that the service 356 // notices and transitions to the FAILED state. We do it by calling notifyFailed directly 357 // because the service does not monitor the state of the future so if the exception is not 358 // caught and forwarded to the service the task would stop executing but the service would 359 // have no idea. 360 service.notifyFailed(e); 361 } finally { 362 lock.unlock(); 363 } 364 } 365 366 // N.B. Only protect cancel and isCancelled because those are the only methods that are 367 // invoked by the AbstractScheduledService. 368 @Override 369 public boolean cancel(boolean mayInterruptIfRunning) { 370 // Ensure that a task cannot be rescheduled while a cancel is ongoing. 371 lock.lock(); 372 try { 373 return currentFuture.cancel(mayInterruptIfRunning); 374 } finally { 375 lock.unlock(); 376 } 377 } 378 379 @Override 380 protected Future<Void> delegate() { 381 throw new UnsupportedOperationException("Only cancel is supported by this future"); 382 } 383 } 384 385 @Override 386 final Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 387 Runnable runnable) { 388 ReschedulableCallable task = new ReschedulableCallable(service, executor, runnable); 389 task.reschedule(); 390 return task; 391 } 392 393 /** 394 * A value object that represents an absolute delay until a task should be invoked. 395 * 396 * @author Luke Sandberg 397 * @since 11.0 398 */ 399 @Beta 400 protected static final class Schedule { 401 402 private final long delay; 403 private final TimeUnit unit; 404 405 /** 406 * @param delay the time from now to delay execution 407 * @param unit the time unit of the delay parameter 408 */ 409 public Schedule(long delay, TimeUnit unit) { 410 this.delay = delay; 411 this.unit = Preconditions.checkNotNull(unit); 412 } 413 } 414 415 /** 416 * Calculates the time at which to next invoke the task. 417 * 418 * <p>This is guaranteed to be called immediately after the task has completed an iteration and 419 * on the same thread as the previous execution of {@link 420 * AbstractScheduledService#runOneIteration}. 421 * 422 * @return a schedule that defines the delay before the next execution. 423 */ 424 protected abstract Schedule getNextSchedule() throws Exception; 425 } 426 }