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