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 017package com.google.common.util.concurrent; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 022 023import com.google.common.annotations.Beta; 024import com.google.common.base.Supplier; 025import com.google.j2objc.annotations.WeakOuter; 026 027import java.util.concurrent.Callable; 028import java.util.concurrent.Executor; 029import java.util.concurrent.Executors; 030import java.util.concurrent.Future; 031import java.util.concurrent.ScheduledExecutorService; 032import java.util.concurrent.ThreadFactory; 033import java.util.concurrent.TimeUnit; 034import java.util.concurrent.TimeoutException; 035import java.util.concurrent.locks.ReentrantLock; 036import java.util.logging.Level; 037import java.util.logging.Logger; 038 039import javax.annotation.concurrent.GuardedBy; 040 041/** 042 * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in 043 * the "running" state need to perform a periodic task. Subclasses can implement {@link #startUp}, 044 * {@link #shutDown} and also a {@link #runOneIteration} method that will be executed periodically. 045 * 046 * <p>This class uses the {@link ScheduledExecutorService} returned from {@link #executor} to run 047 * the {@link #startUp} and {@link #shutDown} methods and also uses that service to schedule the 048 * {@link #runOneIteration} that will be executed periodically as specified by its 049 * {@link Scheduler}. When this service is asked to stop via {@link #stopAsync} it will cancel the 050 * periodic task (but not interrupt it) and wait for it to stop before running the 051 * {@link #shutDown} method. 052 * 053 * <p>Subclasses are guaranteed that the life cycle methods ({@link #runOneIteration}, {@link 054 * #startUp} and {@link #shutDown}) will never run concurrently. Notably, if any execution of {@link 055 * #runOneIteration} takes longer than its schedule defines, then subsequent executions may start 056 * late. Also, all life cycle methods are executed with a lock held, so subclasses can safely 057 * modify shared state without additional synchronization necessary for visibility to later 058 * executions of the life cycle methods. 059 * 060 * <h3>Usage Example</h3> 061 * 062 * <p>Here is a sketch of a service which crawls a website and uses the scheduling capabilities to 063 * rate limit itself. <pre> {@code 064 * class CrawlingService extends AbstractScheduledService { 065 * private Set<Uri> visited; 066 * private Queue<Uri> toCrawl; 067 * protected void startUp() throws Exception { 068 * toCrawl = readStartingUris(); 069 * } 070 * 071 * protected void runOneIteration() throws Exception { 072 * Uri uri = toCrawl.remove(); 073 * Collection<Uri> newUris = crawl(uri); 074 * visited.add(uri); 075 * for (Uri newUri : newUris) { 076 * if (!visited.contains(newUri)) { toCrawl.add(newUri); } 077 * } 078 * } 079 * 080 * protected void shutDown() throws Exception { 081 * saveUris(toCrawl); 082 * } 083 * 084 * protected Scheduler scheduler() { 085 * return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS); 086 * } 087 * }}</pre> 088 * 089 * <p>This class uses the life cycle methods to read in a list of starting URIs and save the set of 090 * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to 091 * rate limit the number of queries we perform. 092 * 093 * @author Luke Sandberg 094 * @since 11.0 095 */ 096@Beta 097public abstract class AbstractScheduledService implements Service { 098 private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); 099 100 /** 101 * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its 102 * task. 103 * 104 * <p>Consider using the {@link #newFixedDelaySchedule} and {@link #newFixedRateSchedule} factory 105 * methods, these provide {@link Scheduler} instances for the common use case of running the 106 * service with a fixed schedule. If more flexibility is needed then consider subclassing 107 * {@link CustomScheduler}. 108 * 109 * @author Luke Sandberg 110 * @since 11.0 111 */ 112 public abstract static class Scheduler { 113 /** 114 * Returns a {@link Scheduler} that schedules the task using the 115 * {@link ScheduledExecutorService#scheduleWithFixedDelay} method. 116 * 117 * @param initialDelay the time to delay first execution 118 * @param delay the delay between the termination of one execution and the commencement of the 119 * next 120 * @param unit the time unit of the initialDelay and delay parameters 121 */ 122 public static Scheduler newFixedDelaySchedule(final long initialDelay, final long delay, 123 final TimeUnit unit) { 124 checkNotNull(unit); 125 checkArgument(delay > 0, "delay must be > 0, found %s", delay); 126 return new Scheduler() { 127 @Override 128 public Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 129 Runnable task) { 130 return executor.scheduleWithFixedDelay(task, initialDelay, delay, unit); 131 } 132 }; 133 } 134 135 /** 136 * Returns a {@link Scheduler} that schedules the task using the 137 * {@link ScheduledExecutorService#scheduleAtFixedRate} method. 138 * 139 * @param initialDelay the time to delay first execution 140 * @param period the period between successive executions of the task 141 * @param unit the time unit of the initialDelay and period parameters 142 */ 143 public static Scheduler newFixedRateSchedule(final long initialDelay, final long period, 144 final TimeUnit unit) { 145 checkNotNull(unit); 146 checkArgument(period > 0, "period must be > 0, found %s", period); 147 return new Scheduler() { 148 @Override 149 public Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 150 Runnable task) { 151 return executor.scheduleAtFixedRate(task, initialDelay, period, unit); 152 } 153 }; 154 } 155 156 /** Schedules the task to run on the provided executor on behalf of the service. */ 157 abstract Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 158 Runnable runnable); 159 160 private Scheduler() {} 161 } 162 163 /* use AbstractService for state management */ 164 private final AbstractService delegate = new AbstractService() { 165 166 // A handle to the running task so that we can stop it when a shutdown has been requested. 167 // These two fields are volatile because their values will be accessed from multiple threads. 168 private volatile Future<?> runningTask; 169 private volatile ScheduledExecutorService executorService; 170 171 // This lock protects the task so we can ensure that none of the template methods (startUp, 172 // shutDown or runOneIteration) run concurrently with one another. 173 // TODO(lukes): why don't we use ListenableFuture to sequence things? Then we could drop the 174 // lock. 175 private final ReentrantLock lock = new ReentrantLock(); 176 177 @WeakOuter 178 class Task implements Runnable { 179 @Override public void run() { 180 lock.lock(); 181 try { 182 if (runningTask.isCancelled()) { 183 // task may have been cancelled while blocked on the lock. 184 return; 185 } 186 AbstractScheduledService.this.runOneIteration(); 187 } catch (Throwable t) { 188 try { 189 shutDown(); 190 } catch (Exception ignored) { 191 logger.log(Level.WARNING, 192 "Error while attempting to shut down the service after failure.", ignored); 193 } 194 notifyFailed(t); 195 runningTask.cancel(false); // prevent future invocations. 196 } finally { 197 lock.unlock(); 198 } 199 } 200 } 201 202 private final Runnable task = new Task(); 203 204 @Override protected final void doStart() { 205 executorService = MoreExecutors.renamingDecorator(executor(), new Supplier<String>() { 206 @Override public String get() { 207 return serviceName() + " " + state(); 208 } 209 }); 210 executorService.execute(new Runnable() { 211 @Override public void run() { 212 lock.lock(); 213 try { 214 startUp(); 215 runningTask = scheduler().schedule(delegate, executorService, task); 216 notifyStarted(); 217 } catch (Throwable t) { 218 notifyFailed(t); 219 if (runningTask != null) { 220 // prevent the task from running if possible 221 runningTask.cancel(false); 222 } 223 } finally { 224 lock.unlock(); 225 } 226 } 227 }); 228 } 229 230 @Override protected final void doStop() { 231 runningTask.cancel(false); 232 executorService.execute(new Runnable() { 233 @Override public void run() { 234 try { 235 lock.lock(); 236 try { 237 if (state() != State.STOPPING) { 238 // This means that the state has changed since we were scheduled. This implies that 239 // an execution of runOneIteration has thrown an exception and we have transitioned 240 // to a failed state, also this means that shutDown has already been called, so we 241 // do not want to call it again. 242 return; 243 } 244 shutDown(); 245 } finally { 246 lock.unlock(); 247 } 248 notifyStopped(); 249 } catch (Throwable t) { 250 notifyFailed(t); 251 } 252 } 253 }); 254 } 255 }; 256 257 /** Constructor for use by subclasses. */ 258 protected AbstractScheduledService() {} 259 260 /** 261 * Run one iteration of the scheduled task. If any invocation of this method throws an exception, 262 * the service will transition to the {@link Service.State#FAILED} state and this method will no 263 * longer be called. 264 */ 265 protected abstract void runOneIteration() throws Exception; 266 267 /** 268 * Start the service. 269 * 270 * <p>By default this method does nothing. 271 */ 272 protected void startUp() throws Exception {} 273 274 /** 275 * Stop the service. This is guaranteed not to run concurrently with {@link #runOneIteration}. 276 * 277 * <p>By default this method does nothing. 278 */ 279 protected void shutDown() throws Exception {} 280 281 /** 282 * Returns the {@link Scheduler} object used to configure this service. This method will only be 283 * called once. 284 */ 285 protected abstract Scheduler scheduler(); 286 287 /** 288 * Returns the {@link ScheduledExecutorService} that will be used to execute the {@link #startUp}, 289 * {@link #runOneIteration} and {@link #shutDown} methods. If this method is overridden the 290 * executor will not be {@linkplain ScheduledExecutorService#shutdown shutdown} when this 291 * service {@linkplain Service.State#TERMINATED terminates} or 292 * {@linkplain Service.State#TERMINATED fails}. Subclasses may override this method to supply a 293 * custom {@link ScheduledExecutorService} instance. This method is guaranteed to only be called 294 * once. 295 * 296 * <p>By default this returns a new {@link ScheduledExecutorService} with a single thread thread 297 * pool that sets the name of the thread to the {@linkplain #serviceName() service name}. 298 * Also, the pool will be {@linkplain ScheduledExecutorService#shutdown() shut down} when the 299 * service {@linkplain Service.State#TERMINATED terminates} or 300 * {@linkplain Service.State#TERMINATED fails}. 301 */ 302 protected ScheduledExecutorService executor() { 303 final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor( 304 new ThreadFactory() { 305 @Override public Thread newThread(Runnable runnable) { 306 return MoreExecutors.newThread(serviceName(), runnable); 307 } 308 }); 309 // Add a listener to shutdown the executor after the service is stopped. This ensures that the 310 // JVM shutdown will not be prevented from exiting after this service has stopped or failed. 311 // Technically this listener is added after start() was called so it is a little gross, but it 312 // is called within doStart() so we know that the service cannot terminate or fail concurrently 313 // with adding this listener so it is impossible to miss an event that we are interested in. 314 addListener(new Listener() { 315 @Override public void terminated(State from) { 316 executor.shutdown(); 317 } 318 @Override public void failed(State from, Throwable failure) { 319 executor.shutdown(); 320 } 321 }, directExecutor()); 322 return executor; 323 } 324 325 /** 326 * Returns the name of this service. {@link AbstractScheduledService} may include the name in 327 * debugging output. 328 * 329 * @since 14.0 330 */ 331 protected String serviceName() { 332 return getClass().getSimpleName(); 333 } 334 335 @Override public String toString() { 336 return serviceName() + " [" + state() + "]"; 337 } 338 339 @Override public final boolean isRunning() { 340 return delegate.isRunning(); 341 } 342 343 @Override public final State state() { 344 return delegate.state(); 345 } 346 347 /** 348 * @since 13.0 349 */ 350 @Override public final void addListener(Listener listener, Executor executor) { 351 delegate.addListener(listener, executor); 352 } 353 354 /** 355 * @since 14.0 356 */ 357 @Override public final Throwable failureCause() { 358 return delegate.failureCause(); 359 } 360 361 /** 362 * @since 15.0 363 */ 364 @Override public final Service startAsync() { 365 delegate.startAsync(); 366 return this; 367 } 368 369 /** 370 * @since 15.0 371 */ 372 @Override public final Service stopAsync() { 373 delegate.stopAsync(); 374 return this; 375 } 376 377 /** 378 * @since 15.0 379 */ 380 @Override public final void awaitRunning() { 381 delegate.awaitRunning(); 382 } 383 384 /** 385 * @since 15.0 386 */ 387 @Override public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { 388 delegate.awaitRunning(timeout, unit); 389 } 390 391 /** 392 * @since 15.0 393 */ 394 @Override public final void awaitTerminated() { 395 delegate.awaitTerminated(); 396 } 397 398 /** 399 * @since 15.0 400 */ 401 @Override public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { 402 delegate.awaitTerminated(timeout, unit); 403 } 404 405 /** 406 * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to 407 * use a dynamically changing schedule. After every execution of the task, assuming it hasn't 408 * been cancelled, the {@link #getNextSchedule} method will be called. 409 * 410 * @author Luke Sandberg 411 * @since 11.0 412 */ 413 @Beta 414 public abstract static class CustomScheduler extends Scheduler { 415 416 /** 417 * A callable class that can reschedule itself using a {@link CustomScheduler}. 418 */ 419 private class ReschedulableCallable extends ForwardingFuture<Void> implements Callable<Void> { 420 421 /** The underlying task. */ 422 private final Runnable wrappedRunnable; 423 424 /** The executor on which this Callable will be scheduled. */ 425 private final ScheduledExecutorService executor; 426 427 /** 428 * The service that is managing this callable. This is used so that failure can be 429 * reported properly. 430 */ 431 private final AbstractService service; 432 433 /** 434 * This lock is used to ensure safe and correct cancellation, it ensures that a new task is 435 * not scheduled while a cancel is ongoing. Also it protects the currentFuture variable to 436 * ensure that it is assigned atomically with being scheduled. 437 */ 438 private final ReentrantLock lock = new ReentrantLock(); 439 440 /** The future that represents the next execution of this task.*/ 441 @GuardedBy("lock") 442 private Future<Void> currentFuture; 443 444 ReschedulableCallable(AbstractService service, ScheduledExecutorService executor, 445 Runnable runnable) { 446 this.wrappedRunnable = runnable; 447 this.executor = executor; 448 this.service = service; 449 } 450 451 @Override 452 public Void call() throws Exception { 453 wrappedRunnable.run(); 454 reschedule(); 455 return null; 456 } 457 458 /** 459 * Atomically reschedules this task and assigns the new future to {@link #currentFuture}. 460 */ 461 public void reschedule() { 462 // invoke the callback outside the lock, prevents some shenanigans. 463 Schedule schedule; 464 try { 465 schedule = CustomScheduler.this.getNextSchedule(); 466 } catch (Throwable t) { 467 service.notifyFailed(t); 468 return; 469 } 470 // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that 471 // cancel calls cancel on the correct future. 2. we want to make sure that the assignment 472 // to currentFuture doesn't race with itself so that currentFuture is assigned in the 473 // correct order. 474 Throwable scheduleFailure = null; 475 lock.lock(); 476 try { 477 if (currentFuture == null || !currentFuture.isCancelled()) { 478 currentFuture = executor.schedule(this, schedule.delay, schedule.unit); 479 } 480 } catch (Throwable e) { 481 // If an exception is thrown by the subclass then we need to make sure that the service 482 // notices and transitions to the FAILED state. We do it by calling notifyFailed directly 483 // because the service does not monitor the state of the future so if the exception is not 484 // caught and forwarded to the service the task would stop executing but the service would 485 // have no idea. 486 // TODO(lukes): consider building everything in terms of ListenableScheduledFuture then 487 // the AbstractService could monitor the future directly. Rescheduling is still hard... 488 // but it would help with some of these lock ordering issues. 489 scheduleFailure = e; 490 } finally { 491 lock.unlock(); 492 } 493 // Call notifyFailed outside the lock to avoid lock ordering issues. 494 if (scheduleFailure != null) { 495 service.notifyFailed(scheduleFailure); 496 } 497 } 498 499 // N.B. Only protect cancel and isCancelled because those are the only methods that are 500 // invoked by the AbstractScheduledService. 501 @Override 502 public boolean cancel(boolean mayInterruptIfRunning) { 503 // Ensure that a task cannot be rescheduled while a cancel is ongoing. 504 lock.lock(); 505 try { 506 return currentFuture.cancel(mayInterruptIfRunning); 507 } finally { 508 lock.unlock(); 509 } 510 } 511 512 @Override 513 public boolean isCancelled() { 514 lock.lock(); 515 try { 516 return currentFuture.isCancelled(); 517 } finally { 518 lock.unlock(); 519 } 520 } 521 522 @Override 523 protected Future<Void> delegate() { 524 throw new UnsupportedOperationException( 525 "Only cancel and isCancelled is supported by this future"); 526 } 527 } 528 529 @Override 530 final Future<?> schedule(AbstractService service, ScheduledExecutorService executor, 531 Runnable runnable) { 532 ReschedulableCallable task = new ReschedulableCallable(service, executor, runnable); 533 task.reschedule(); 534 return task; 535 } 536 537 /** 538 * A value object that represents an absolute delay until a task should be invoked. 539 * 540 * @author Luke Sandberg 541 * @since 11.0 542 */ 543 @Beta 544 protected static final class Schedule { 545 546 private final long delay; 547 private final TimeUnit unit; 548 549 /** 550 * @param delay the time from now to delay execution 551 * @param unit the time unit of the delay parameter 552 */ 553 public Schedule(long delay, TimeUnit unit) { 554 this.delay = delay; 555 this.unit = checkNotNull(unit); 556 } 557 } 558 559 /** 560 * Calculates the time at which to next invoke the task. 561 * 562 * <p>This is guaranteed to be called immediately after the task has completed an iteration and 563 * on the same thread as the previous execution of {@link 564 * AbstractScheduledService#runOneIteration}. 565 * 566 * @return a schedule that defines the delay before the next execution. 567 */ 568 protected abstract Schedule getNextSchedule() throws Exception; 569 } 570}