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