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