001/* 002 * Copyright (C) 2011 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.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkNotNull; 019import static com.google.common.util.concurrent.Futures.immediateCancelledFuture; 020import static com.google.common.util.concurrent.Internal.toNanosSaturated; 021import static com.google.common.util.concurrent.MoreExecutors.directExecutor; 022import static com.google.common.util.concurrent.Platform.restoreInterruptIfIsInterruptedException; 023import static java.util.Objects.requireNonNull; 024import static java.util.concurrent.TimeUnit.NANOSECONDS; 025 026import com.google.common.annotations.GwtIncompatible; 027import com.google.common.annotations.J2ktIncompatible; 028import com.google.errorprone.annotations.CanIgnoreReturnValue; 029import com.google.errorprone.annotations.concurrent.GuardedBy; 030import com.google.j2objc.annotations.WeakOuter; 031import java.time.Duration; 032import java.util.concurrent.Callable; 033import java.util.concurrent.Executor; 034import java.util.concurrent.Executors; 035import java.util.concurrent.Future; 036import java.util.concurrent.ScheduledExecutorService; 037import java.util.concurrent.ScheduledFuture; 038import java.util.concurrent.ThreadFactory; 039import java.util.concurrent.TimeUnit; 040import java.util.concurrent.TimeoutException; 041import java.util.concurrent.locks.ReentrantLock; 042import java.util.logging.Level; 043import java.util.logging.Logger; 044import javax.annotation.CheckForNull; 045import org.checkerframework.checker.nullness.qual.Nullable; 046 047/** 048 * Base class for services that can implement {@link #startUp} and {@link #shutDown} but while in 049 * the "running" state need to perform a periodic task. Subclasses can implement {@link #startUp}, 050 * {@link #shutDown} and also a {@link #runOneIteration} method that will be executed periodically. 051 * 052 * <p>This class uses the {@link ScheduledExecutorService} returned from {@link #executor} to run 053 * the {@link #startUp} and {@link #shutDown} methods and also uses that service to schedule the 054 * {@link #runOneIteration} that will be executed periodically as specified by its {@link 055 * Scheduler}. When this service is asked to stop via {@link #stopAsync} it will cancel the periodic 056 * task (but not interrupt it) and wait for it to stop before running the {@link #shutDown} method. 057 * 058 * <p>Subclasses are guaranteed that the life cycle methods ({@link #runOneIteration}, {@link 059 * #startUp} and {@link #shutDown}) will never run concurrently. Notably, if any execution of {@link 060 * #runOneIteration} takes longer than its schedule defines, then subsequent executions may start 061 * late. Also, all life cycle methods are executed with a lock held, so subclasses can safely modify 062 * shared state without additional synchronization necessary for visibility to later executions of 063 * the life cycle methods. 064 * 065 * <h3>Usage Example</h3> 066 * 067 * <p>Here is a sketch of a service which crawls a website and uses the scheduling capabilities to 068 * rate limit itself. 069 * 070 * <pre>{@code 071 * class CrawlingService extends AbstractScheduledService { 072 * private Set<Uri> visited; 073 * private Queue<Uri> toCrawl; 074 * protected void startUp() throws Exception { 075 * toCrawl = readStartingUris(); 076 * } 077 * 078 * protected void runOneIteration() throws Exception { 079 * Uri uri = toCrawl.remove(); 080 * Collection<Uri> newUris = crawl(uri); 081 * visited.add(uri); 082 * for (Uri newUri : newUris) { 083 * if (!visited.contains(newUri)) { toCrawl.add(newUri); } 084 * } 085 * } 086 * 087 * protected void shutDown() throws Exception { 088 * saveUris(toCrawl); 089 * } 090 * 091 * protected Scheduler scheduler() { 092 * return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS); 093 * } 094 * } 095 * }</pre> 096 * 097 * <p>This class uses the life cycle methods to read in a list of starting URIs and save the set of 098 * outstanding URIs when shutting down. Also, it takes advantage of the scheduling functionality to 099 * rate limit the number of queries we perform. 100 * 101 * @author Luke Sandberg 102 * @since 11.0 103 */ 104@GwtIncompatible 105@J2ktIncompatible 106@ElementTypesAreNonnullByDefault 107public abstract class AbstractScheduledService implements Service { 108 private static final Logger logger = Logger.getLogger(AbstractScheduledService.class.getName()); 109 110 /** 111 * A scheduler defines the policy for how the {@link AbstractScheduledService} should run its 112 * task. 113 * 114 * <p>Consider using the {@link #newFixedDelaySchedule} and {@link #newFixedRateSchedule} factory 115 * methods, these provide {@link Scheduler} instances for the common use case of running the 116 * service with a fixed schedule. If more flexibility is needed then consider subclassing {@link 117 * CustomScheduler}. 118 * 119 * @author Luke Sandberg 120 * @since 11.0 121 */ 122 public abstract static class Scheduler { 123 /** 124 * Returns a {@link Scheduler} that schedules the task using the {@link 125 * ScheduledExecutorService#scheduleWithFixedDelay} method. 126 * 127 * @param initialDelay the time to delay first execution 128 * @param delay the delay between the termination of one execution and the commencement of the 129 * next 130 * @since 28.0 131 */ 132 public static Scheduler newFixedDelaySchedule(Duration initialDelay, Duration delay) { 133 return newFixedDelaySchedule( 134 toNanosSaturated(initialDelay), toNanosSaturated(delay), NANOSECONDS); 135 } 136 137 /** 138 * Returns a {@link Scheduler} that schedules the task using the {@link 139 * ScheduledExecutorService#scheduleWithFixedDelay} method. 140 * 141 * @param initialDelay the time to delay first execution 142 * @param delay the delay between the termination of one execution and the commencement of the 143 * next 144 * @param unit the time unit of the initialDelay and delay parameters 145 */ 146 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 147 public static Scheduler newFixedDelaySchedule( 148 final long initialDelay, final long delay, final TimeUnit unit) { 149 checkNotNull(unit); 150 checkArgument(delay > 0, "delay must be > 0, found %s", delay); 151 return new Scheduler() { 152 @Override 153 public Cancellable schedule( 154 AbstractService service, ScheduledExecutorService executor, Runnable task) { 155 return new FutureAsCancellable( 156 executor.scheduleWithFixedDelay(task, initialDelay, delay, unit)); 157 } 158 }; 159 } 160 161 /** 162 * Returns a {@link Scheduler} that schedules the task using the {@link 163 * ScheduledExecutorService#scheduleAtFixedRate} method. 164 * 165 * @param initialDelay the time to delay first execution 166 * @param period the period between successive executions of the task 167 * @since 28.0 168 */ 169 public static Scheduler newFixedRateSchedule(Duration initialDelay, Duration period) { 170 return newFixedRateSchedule( 171 toNanosSaturated(initialDelay), toNanosSaturated(period), NANOSECONDS); 172 } 173 174 /** 175 * Returns a {@link Scheduler} that schedules the task using the {@link 176 * ScheduledExecutorService#scheduleAtFixedRate} method. 177 * 178 * @param initialDelay the time to delay first execution 179 * @param period the period between successive executions of the task 180 * @param unit the time unit of the initialDelay and period parameters 181 */ 182 @SuppressWarnings("GoodTime") // should accept a java.time.Duration 183 public static Scheduler newFixedRateSchedule( 184 final long initialDelay, final long period, final TimeUnit unit) { 185 checkNotNull(unit); 186 checkArgument(period > 0, "period must be > 0, found %s", period); 187 return new Scheduler() { 188 @Override 189 public Cancellable schedule( 190 AbstractService service, ScheduledExecutorService executor, Runnable task) { 191 return new FutureAsCancellable( 192 executor.scheduleAtFixedRate(task, initialDelay, period, unit)); 193 } 194 }; 195 } 196 197 /** Schedules the task to run on the provided executor on behalf of the service. */ 198 abstract Cancellable schedule( 199 AbstractService service, ScheduledExecutorService executor, Runnable runnable); 200 201 private Scheduler() {} 202 } 203 204 /* use AbstractService for state management */ 205 private final AbstractService delegate = new ServiceDelegate(); 206 207 @WeakOuter 208 private final class ServiceDelegate extends AbstractService { 209 210 // A handle to the running task so that we can stop it when a shutdown has been requested. 211 // These two fields are volatile because their values will be accessed from multiple threads. 212 @CheckForNull private volatile Cancellable runningTask; 213 @CheckForNull private volatile ScheduledExecutorService executorService; 214 215 // This lock protects the task so we can ensure that none of the template methods (startUp, 216 // shutDown or runOneIteration) run concurrently with one another. 217 // TODO(lukes): why don't we use ListenableFuture to sequence things? Then we could drop the 218 // lock. 219 private final ReentrantLock lock = new ReentrantLock(); 220 221 @WeakOuter 222 class Task implements Runnable { 223 @Override 224 public void run() { 225 lock.lock(); 226 try { 227 /* 228 * requireNonNull is safe because Task isn't run (or at least it doesn't succeed in taking 229 * the lock) until after it's scheduled and the runningTask field is set. 230 */ 231 if (requireNonNull(runningTask).isCancelled()) { 232 // task may have been cancelled while blocked on the lock. 233 return; 234 } 235 AbstractScheduledService.this.runOneIteration(); 236 } catch (Throwable t) { 237 restoreInterruptIfIsInterruptedException(t); 238 try { 239 shutDown(); 240 } catch (Exception ignored) { 241 restoreInterruptIfIsInterruptedException(ignored); 242 logger.log( 243 Level.WARNING, 244 "Error while attempting to shut down the service after failure.", 245 ignored); 246 } 247 notifyFailed(t); 248 // requireNonNull is safe now, just as it was above. 249 requireNonNull(runningTask).cancel(false); // prevent future invocations. 250 } finally { 251 lock.unlock(); 252 } 253 } 254 } 255 256 private final Runnable task = new Task(); 257 258 @Override 259 protected final void doStart() { 260 executorService = 261 MoreExecutors.renamingDecorator(executor(), () -> serviceName() + " " + state()); 262 executorService.execute( 263 () -> { 264 lock.lock(); 265 try { 266 startUp(); 267 /* 268 * requireNonNull is safe because executorService is never cleared after the 269 * assignment above. 270 */ 271 requireNonNull(executorService); 272 runningTask = scheduler().schedule(delegate, executorService, task); 273 notifyStarted(); 274 } catch (Throwable t) { 275 restoreInterruptIfIsInterruptedException(t); 276 notifyFailed(t); 277 if (runningTask != null) { 278 // prevent the task from running if possible 279 runningTask.cancel(false); 280 } 281 } finally { 282 lock.unlock(); 283 } 284 }); 285 } 286 287 @Override 288 protected final void doStop() { 289 // Both requireNonNull calls are safe because doStop can run only after a successful doStart. 290 requireNonNull(runningTask); 291 requireNonNull(executorService); 292 runningTask.cancel(false); 293 executorService.execute( 294 () -> { 295 try { 296 lock.lock(); 297 try { 298 if (state() != State.STOPPING) { 299 // This means that the state has changed since we were scheduled. This implies 300 // that an execution of runOneIteration has thrown an exception and we have 301 // transitioned to a failed state, also this means that shutDown has already 302 // been called, so we do not want to call it again. 303 return; 304 } 305 shutDown(); 306 } finally { 307 lock.unlock(); 308 } 309 notifyStopped(); 310 } catch (Throwable t) { 311 restoreInterruptIfIsInterruptedException(t); 312 notifyFailed(t); 313 } 314 }); 315 } 316 317 @Override 318 public String toString() { 319 return AbstractScheduledService.this.toString(); 320 } 321 } 322 323 /** Constructor for use by subclasses. */ 324 protected AbstractScheduledService() {} 325 326 /** 327 * Run one iteration of the scheduled task. If any invocation of this method throws an exception, 328 * the service will transition to the {@link Service.State#FAILED} state and this method will no 329 * longer be called. 330 */ 331 protected abstract void runOneIteration() throws Exception; 332 333 /** 334 * Start the service. 335 * 336 * <p>By default this method does nothing. 337 */ 338 protected void startUp() throws Exception {} 339 340 /** 341 * Stop the service. This is guaranteed not to run concurrently with {@link #runOneIteration}. 342 * 343 * <p>By default this method does nothing. 344 */ 345 protected void shutDown() throws Exception {} 346 347 /** 348 * Returns the {@link Scheduler} object used to configure this service. This method will only be 349 * called once. 350 */ 351 // TODO(cpovirk): @ForOverride 352 protected abstract Scheduler scheduler(); 353 354 /** 355 * Returns the {@link ScheduledExecutorService} that will be used to execute the {@link #startUp}, 356 * {@link #runOneIteration} and {@link #shutDown} methods. If this method is overridden the 357 * executor will not be {@linkplain ScheduledExecutorService#shutdown shutdown} when this service 358 * {@linkplain Service.State#TERMINATED terminates} or {@linkplain Service.State#TERMINATED 359 * fails}. Subclasses may override this method to supply a custom {@link ScheduledExecutorService} 360 * instance. This method is guaranteed to only be called once. 361 * 362 * <p>By default this returns a new {@link ScheduledExecutorService} with a single thread pool 363 * that sets the name of the thread to the {@linkplain #serviceName() service name}. Also, the 364 * pool will be {@linkplain ScheduledExecutorService#shutdown() shut down} when the service 365 * {@linkplain Service.State#TERMINATED terminates} or {@linkplain Service.State#TERMINATED 366 * fails}. 367 */ 368 protected ScheduledExecutorService executor() { 369 @WeakOuter 370 class ThreadFactoryImpl implements ThreadFactory { 371 @Override 372 public Thread newThread(Runnable runnable) { 373 return MoreExecutors.newThread(serviceName(), runnable); 374 } 375 } 376 final ScheduledExecutorService executor = 377 Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl()); 378 // Add a listener to shut down the executor after the service is stopped. This ensures that the 379 // JVM shutdown will not be prevented from exiting after this service has stopped or failed. 380 // Technically this listener is added after start() was called so it is a little gross, but it 381 // is called within doStart() so we know that the service cannot terminate or fail concurrently 382 // with adding this listener so it is impossible to miss an event that we are interested in. 383 addListener( 384 new Listener() { 385 @Override 386 public void terminated(State from) { 387 executor.shutdown(); 388 } 389 390 @Override 391 public void failed(State from, Throwable failure) { 392 executor.shutdown(); 393 } 394 }, 395 directExecutor()); 396 return executor; 397 } 398 399 /** 400 * Returns the name of this service. {@link AbstractScheduledService} may include the name in 401 * debugging output. 402 * 403 * @since 14.0 404 */ 405 protected String serviceName() { 406 return getClass().getSimpleName(); 407 } 408 409 @Override 410 public String toString() { 411 return serviceName() + " [" + state() + "]"; 412 } 413 414 @Override 415 public final boolean isRunning() { 416 return delegate.isRunning(); 417 } 418 419 @Override 420 public final State state() { 421 return delegate.state(); 422 } 423 424 /** @since 13.0 */ 425 @Override 426 public final void addListener(Listener listener, Executor executor) { 427 delegate.addListener(listener, executor); 428 } 429 430 /** @since 14.0 */ 431 @Override 432 public final Throwable failureCause() { 433 return delegate.failureCause(); 434 } 435 436 /** @since 15.0 */ 437 @CanIgnoreReturnValue 438 @Override 439 public final Service startAsync() { 440 delegate.startAsync(); 441 return this; 442 } 443 444 /** @since 15.0 */ 445 @CanIgnoreReturnValue 446 @Override 447 public final Service stopAsync() { 448 delegate.stopAsync(); 449 return this; 450 } 451 452 /** @since 15.0 */ 453 @Override 454 public final void awaitRunning() { 455 delegate.awaitRunning(); 456 } 457 458 /** @since 28.0 */ 459 @Override 460 public final void awaitRunning(Duration timeout) throws TimeoutException { 461 Service.super.awaitRunning(timeout); 462 } 463 464 /** @since 15.0 */ 465 @Override 466 public final void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException { 467 delegate.awaitRunning(timeout, unit); 468 } 469 470 /** @since 15.0 */ 471 @Override 472 public final void awaitTerminated() { 473 delegate.awaitTerminated(); 474 } 475 476 /** @since 28.0 */ 477 @Override 478 public final void awaitTerminated(Duration timeout) throws TimeoutException { 479 Service.super.awaitTerminated(timeout); 480 } 481 482 /** @since 15.0 */ 483 @Override 484 public final void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException { 485 delegate.awaitTerminated(timeout, unit); 486 } 487 488 interface Cancellable { 489 void cancel(boolean mayInterruptIfRunning); 490 491 boolean isCancelled(); 492 } 493 494 private static final class FutureAsCancellable implements Cancellable { 495 private final Future<?> delegate; 496 497 FutureAsCancellable(Future<?> delegate) { 498 this.delegate = delegate; 499 } 500 501 @Override 502 public void cancel(boolean mayInterruptIfRunning) { 503 delegate.cancel(mayInterruptIfRunning); 504 } 505 506 @Override 507 public boolean isCancelled() { 508 return delegate.isCancelled(); 509 } 510 } 511 512 /** 513 * A {@link Scheduler} that provides a convenient way for the {@link AbstractScheduledService} to 514 * use a dynamically changing schedule. After every execution of the task, assuming it hasn't been 515 * cancelled, the {@link #getNextSchedule} method will be called. 516 * 517 * @author Luke Sandberg 518 * @since 11.0 519 */ 520 public abstract static class CustomScheduler extends Scheduler { 521 522 /** A callable class that can reschedule itself using a {@link CustomScheduler}. */ 523 private final class ReschedulableCallable implements Callable<@Nullable Void> { 524 525 /** The underlying task. */ 526 private final Runnable wrappedRunnable; 527 528 /** The executor on which this Callable will be scheduled. */ 529 private final ScheduledExecutorService executor; 530 531 /** 532 * The service that is managing this callable. This is used so that failure can be reported 533 * properly. 534 */ 535 /* 536 * This reference is part of a reference cycle, which is typically something we want to avoid 537 * under j2objc -- but it is not detected by our j2objc cycle test. The cycle: 538 * 539 * - CustomScheduler.service contains an instance of ServiceDelegate. (It needs it so that it 540 * can call notifyFailed.) 541 * 542 * - ServiceDelegate.runningTask contains an instance of ReschedulableCallable (at least in 543 * the case that the service is using CustomScheduler). (It needs it so that it can cancel 544 * the task and detect whether it has been cancelled.) 545 * 546 * - ReschedulableCallable has a reference back to its enclosing CustomScheduler. (It needs it 547 * so that it can call getNextSchedule). 548 * 549 * Maybe there is a way to avoid this cycle. But we think the cycle is safe enough to ignore: 550 * Each task is retained for only as long as it is running -- so it's retained only as long as 551 * it would already be retained by the underlying executor. 552 * 553 * If the cycle test starts reporting this cycle in the future, we should add an entry to 554 * cycle_suppress_list.txt. 555 */ 556 private final AbstractService service; 557 558 /** 559 * This lock is used to ensure safe and correct cancellation, it ensures that a new task is 560 * not scheduled while a cancel is ongoing. Also it protects the currentFuture variable to 561 * ensure that it is assigned atomically with being scheduled. 562 */ 563 private final ReentrantLock lock = new ReentrantLock(); 564 565 /** The future that represents the next execution of this task. */ 566 @GuardedBy("lock") 567 @CheckForNull 568 private SupplantableFuture cancellationDelegate; 569 570 ReschedulableCallable( 571 AbstractService service, ScheduledExecutorService executor, Runnable runnable) { 572 this.wrappedRunnable = runnable; 573 this.executor = executor; 574 this.service = service; 575 } 576 577 @Override 578 @CheckForNull 579 public Void call() throws Exception { 580 wrappedRunnable.run(); 581 reschedule(); 582 return null; 583 } 584 585 /** 586 * Atomically reschedules this task and assigns the new future to {@link 587 * #cancellationDelegate}. 588 */ 589 @CanIgnoreReturnValue 590 public Cancellable reschedule() { 591 // invoke the callback outside the lock, prevents some shenanigans. 592 Schedule schedule; 593 try { 594 schedule = CustomScheduler.this.getNextSchedule(); 595 } catch (Throwable t) { 596 restoreInterruptIfIsInterruptedException(t); 597 service.notifyFailed(t); 598 return new FutureAsCancellable(immediateCancelledFuture()); 599 } 600 // We reschedule ourselves with a lock held for two reasons. 1. we want to make sure that 601 // cancel calls cancel on the correct future. 2. we want to make sure that the assignment 602 // to currentFuture doesn't race with itself so that currentFuture is assigned in the 603 // correct order. 604 Throwable scheduleFailure = null; 605 Cancellable toReturn; 606 lock.lock(); 607 try { 608 toReturn = initializeOrUpdateCancellationDelegate(schedule); 609 } catch (RuntimeException | Error e) { 610 // If an exception is thrown by the subclass then we need to make sure that the service 611 // notices and transitions to the FAILED state. We do it by calling notifyFailed directly 612 // because the service does not monitor the state of the future so if the exception is not 613 // caught and forwarded to the service the task would stop executing but the service would 614 // have no idea. 615 // TODO(lukes): consider building everything in terms of ListenableScheduledFuture then 616 // the AbstractService could monitor the future directly. Rescheduling is still hard... 617 // but it would help with some of these lock ordering issues. 618 scheduleFailure = e; 619 toReturn = new FutureAsCancellable(immediateCancelledFuture()); 620 } finally { 621 lock.unlock(); 622 } 623 // Call notifyFailed outside the lock to avoid lock ordering issues. 624 if (scheduleFailure != null) { 625 service.notifyFailed(scheduleFailure); 626 } 627 return toReturn; 628 } 629 630 @GuardedBy("lock") 631 /* 632 * The GuardedBy checker warns us that we're not holding cancellationDelegate.lock. But in 633 * fact we are holding it because it is the same as this.lock, which we know we are holding, 634 * thanks to @GuardedBy above. (cancellationDelegate.lock is initialized to this.lock in the 635 * call to `new SupplantableFuture` below.) 636 */ 637 @SuppressWarnings("GuardedBy") 638 private Cancellable initializeOrUpdateCancellationDelegate(Schedule schedule) { 639 if (cancellationDelegate == null) { 640 return cancellationDelegate = new SupplantableFuture(lock, submitToExecutor(schedule)); 641 } 642 if (!cancellationDelegate.currentFuture.isCancelled()) { 643 cancellationDelegate.currentFuture = submitToExecutor(schedule); 644 } 645 return cancellationDelegate; 646 } 647 648 private ScheduledFuture<@Nullable Void> submitToExecutor(Schedule schedule) { 649 return executor.schedule(this, schedule.delay, schedule.unit); 650 } 651 } 652 653 /** 654 * Contains the most recently submitted {@code Future}, which may be cancelled or updated, 655 * always under a lock. 656 */ 657 private static final class SupplantableFuture implements Cancellable { 658 private final ReentrantLock lock; 659 660 @GuardedBy("lock") 661 private Future<@Nullable Void> currentFuture; 662 663 SupplantableFuture(ReentrantLock lock, Future<@Nullable Void> currentFuture) { 664 this.lock = lock; 665 this.currentFuture = currentFuture; 666 } 667 668 @Override 669 public void cancel(boolean mayInterruptIfRunning) { 670 /* 671 * Lock to ensure that a task cannot be rescheduled while a cancel is ongoing. 672 * 673 * In theory, cancel() could execute arbitrary listeners -- bad to do while holding a lock. 674 * However, we don't expose currentFuture to users, so they can't attach listeners. And the 675 * Future might not even be a ListenableFuture, just a plain Future. That said, similar 676 * problems can exist with methods like FutureTask.done(), not to mention slow calls to 677 * Thread.interrupt() (as discussed in InterruptibleTask). At the end of the day, it's 678 * unlikely that cancel() will be slow, so we can probably get away with calling it while 679 * holding a lock. Still, it would be nice to avoid somehow. 680 */ 681 lock.lock(); 682 try { 683 currentFuture.cancel(mayInterruptIfRunning); 684 } finally { 685 lock.unlock(); 686 } 687 } 688 689 @Override 690 public boolean isCancelled() { 691 lock.lock(); 692 try { 693 return currentFuture.isCancelled(); 694 } finally { 695 lock.unlock(); 696 } 697 } 698 } 699 700 @Override 701 final Cancellable schedule( 702 AbstractService service, ScheduledExecutorService executor, Runnable runnable) { 703 return new ReschedulableCallable(service, executor, runnable).reschedule(); 704 } 705 706 /** 707 * A value object that represents an absolute delay until a task should be invoked. 708 * 709 * @author Luke Sandberg 710 * @since 11.0 711 */ 712 protected static final class Schedule { 713 714 private final long delay; 715 private final TimeUnit unit; 716 717 /** 718 * @param delay the time from now to delay execution 719 * @param unit the time unit of the delay parameter 720 */ 721 public Schedule(long delay, TimeUnit unit) { 722 this.delay = delay; 723 this.unit = checkNotNull(unit); 724 } 725 726 /** 727 * @param delay the time from now to delay execution 728 * @since 31.1 729 */ 730 public Schedule(Duration delay) { 731 this(toNanosSaturated(delay), NANOSECONDS); 732 } 733 } 734 735 /** 736 * Calculates the time at which to next invoke the task. 737 * 738 * <p>This is guaranteed to be called immediately after the task has completed an iteration and 739 * on the same thread as the previous execution of {@link 740 * AbstractScheduledService#runOneIteration}. 741 * 742 * @return a schedule that defines the delay before the next execution. 743 */ 744 // TODO(cpovirk): @ForOverride 745 protected abstract Schedule getNextSchedule() throws Exception; 746 } 747}