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