1 /* 2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.concurrent; 27 28 import javafx.beans.property.BooleanProperty; 29 import javafx.beans.property.IntegerProperty; 30 import javafx.beans.property.ObjectProperty; 31 import javafx.beans.property.ReadOnlyIntegerProperty; 32 import javafx.beans.property.ReadOnlyIntegerWrapper; 33 import javafx.beans.property.ReadOnlyObjectProperty; 34 import javafx.beans.property.ReadOnlyObjectWrapper; 35 import javafx.beans.property.SimpleBooleanProperty; 36 import javafx.beans.property.SimpleIntegerProperty; 37 import javafx.beans.property.SimpleObjectProperty; 38 import javafx.util.Callback; 39 import javafx.util.Duration; 40 import java.util.Timer; 41 import java.util.TimerTask; 42 43 /** 44 * <p>The ScheduledService is a {@link Service} which will automatically restart 45 * itself after a successful execution, and under some conditions will 46 * restart even in case of failure. A new ScheduledService begins in 47 * the READY state, just as a normal Service. After calling 48 * <code>start</code> or <code>restart</code>, the ScheduledService will 49 * enter the SCHEDULED state for the duration specified by <code>delay</code>. 50 * </p> 51 * 52 * <p>Once RUNNING, the ScheduledService will execute its Task. On successful 53 * completion, the ScheduledService will transition to the SUCCEEDED state, 54 * and then to the READY state and back to the SCHEDULED state. The amount 55 * of time the ScheduledService will remain in this state depends on the 56 * amount of time between the last state transition to RUNNING, and the 57 * current time, and the <code>period</code>. In short, the <code>period</code> 58 * defines the minimum amount of time from the start of one run and the start of 59 * the next. If the previous execution completed before <code>period</code> expires, 60 * then the ScheduledService will remain in the SCHEDULED state until the period 61 * expires. If on the other hand the execution took longer than the 62 * specified period, then the ScheduledService will immediately transition 63 * back to RUNNING. </p> 64 * 65 * <p>If, while RUNNING, the ScheduledService's Task throws an error or in 66 * some other way ends up transitioning to FAILED, then the ScheduledService 67 * will either restart or quit, depending on the values for 68 * <code>backoffStrategy</code>, <code>restartOnFailure</code>, and 69 * <code>maximumFailureCount</code>.</p> 70 * 71 * <p>If a failure occurs and <code>restartOnFailure</code> is false, then 72 * the ScheduledService will transition to FAILED and will stop. To restart 73 * a failed ScheduledService, you must call restart manually.</p> 74 * 75 * <p>If a failure occurs and <code>restartOnFailure</code> is true, then 76 * the the ScheduledService <em>may</em> restart automatically. First, 77 * the result of calling <code>backoffStrategy</code> will become the 78 * new <code>cumulativePeriod</code>. In this way, after each failure, you can cause 79 * the service to wait a longer and longer period of time before restarting. 80 * Once the task completes successfully, the cumulativePeriod is reset to 81 * the value of <code>period</code>.</p> 82 * 83 * <p>ScheduledService defines static EXPONENTIAL_BACKOFF_STRATEGY and LOGARITHMIC_BACKOFF_STRATEGY 84 * implementations, of which LOGARITHMIC_BACKOFF_STRATEGY is the default value for 85 * backoffStrategy. After <code>maximumFailureCount</code> is reached, the 86 * ScheduledService will transition to FAILED in exactly the same way as if 87 * <code>restartOnFailure</code> were false.</p> 88 * 89 * <p>If the <code>period</code> or <code>delay</code> is changed while the 90 * ScheduledService is running, the new values will be taken into account on the 91 * next iteration. For example, if the <code>period</code> is increased, then the next time the 92 * ScheduledService enters the SCHEDULED state, the new <code>period</code> will be used. 93 * Likewise, if the <code>delay</code> is changed, the new value will be honored on 94 * the next restart or reset/start.</p> 95 * 96 * The ScheduledService is typically used for use cases that involve polling. For 97 * example, you may want to ping a server on a regular basis to see if there are 98 * any updates. Such as ScheduledService might be implemented like this: 99 * 100 * <pre><code> 101 * {@literal ScheduledService<Document> svc = new ScheduledService<Document>()} { 102 * {@literal protected Task<Document> createTask()} { 103 * {@literal return new Task<Document>()} { 104 * protected Document call() { 105 * // Connect to a Server 106 * // Get the XML document 107 * // Parse it into a document 108 * return document; 109 * } 110 * }; 111 * } 112 * }; 113 * svc.setPeriod(Duration.seconds(1)); 114 * </code></pre> 115 * 116 * This example will ping the remote server every 1 second. 117 * 118 * <p>Timing for this class is not absolutely reliable. A very busy event thread might introduce some timing 119 * lag into the beginning of the execution of the background Task, so very small values for the period or 120 * delay are likely to be inaccurate. A delay or period in the hundreds of milliseconds or larger should be 121 * fairly reliable.</p> 122 * 123 * <p>The ScheduledService in its default configuration has a default <code>period</code> of 0 and a 124 * default <code>delay</code> of 0. This will cause the ScheduledService to execute the task immediately 125 * upon {@link #start()}, and re-executing immediately upon successful completion.</p> 126 * 127 * <p>For this purposes of this class, any Duration that answers true to {@link javafx.util.Duration#isUnknown()} 128 * will treat that duration as if it were Duration.ZERO. Likewise, any Duration which answers true 129 * to {@link javafx.util.Duration#isIndefinite()} will be treated as if it were a duration of Double.MAX_VALUE 130 * milliseconds. Any null Duration is treated as Duration.ZERO. Any custom implementation of an backoff strategy 131 * callback must be prepared to handle these different potential values.</p> 132 * 133 * <p>The ScheduledService introduces a new property called {@link #lastValue}. The lastValue is the value that 134 * was last successfully computed. Because a Service clears its {@code value} property on each run, and 135 * because the ScheduledService will reschedule a run immediately after completion (unless it enters the 136 * cancelled or failed states), the value property is not overly useful on a ScheduledService. In most cases 137 * you will want to instead use the value returned by lastValue.</p> 138 * 139 * <b>Implementer Note:</b> The {@link #ready()}, {@link #scheduled()}, {@link #running()}, {@link #succeeded()}, 140 * {@link #cancelled()}, and {@link #failed()} methods are implemented in this class. Subclasses which also 141 * override these methods must take care to invoke the super implementation. 142 * 143 * @param <V> The computed value of the ScheduledService 144 * @since JavaFX 8.0 145 */ 146 public abstract class ScheduledService<V> extends Service<V> { 147 /** 148 * A Callback implementation for the <code>backoffStrategy</code> property which 149 * will exponentially backoff the period between re-executions in the case of 150 * a failure. This computation takes the original period and the number of 151 * consecutive failures and computes the backoff amount from that information. 152 * 153 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 154 * the result of this method will simply be {@code Math.exp(currentFailureCount)}. In all other cases, 155 * the returned value is the same as {@code period + (period * Math.exp(currentFailureCount))}.</p> 156 */ 157 public static final Callback<ScheduledService<?>, Duration> EXPONENTIAL_BACKOFF_STRATEGY 158 = new Callback<ScheduledService<?>, Duration>() { 159 @Override public Duration call(ScheduledService<?> service) { 160 if (service == null) return Duration.ZERO; 161 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 162 final double x = service.getCurrentFailureCount(); 163 return Duration.millis(period == 0 ? Math.exp(x) : period + (period * Math.exp(x))); 164 } 165 }; 166 167 /** 168 * A Callback implementation for the <code>backoffStrategy</code> property which 169 * will logarithmically backoff the period between re-executions in the case of 170 * a failure. This computation takes the original period and the number of 171 * consecutive failures and computes the backoff amount from that information. 172 * 173 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 174 * the result of this method will simply be {@code Math.log1p(currentFailureCount)}. In all other cases, 175 * the returned value is the same as {@code period + (period * Math.log1p(currentFailureCount))}.</p> 176 */ 177 public static final Callback<ScheduledService<?>, Duration> LOGARITHMIC_BACKOFF_STRATEGY 178 = new Callback<ScheduledService<?>, Duration>() { 179 @Override public Duration call(ScheduledService<?> service) { 180 if (service == null) return Duration.ZERO; 181 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 182 final double x = service.getCurrentFailureCount(); 183 return Duration.millis(period == 0 ? Math.log1p(x) : period + (period * Math.log1p(x))); 184 } 185 }; 186 187 /** 188 * A Callback implementation for the <code>backoffStrategy</code> property which 189 * will linearly backoff the period between re-executions in the case of 190 * a failure. This computation takes the original period and the number of 191 * consecutive failures and computes the backoff amount from that information. 192 * 193 * <p>If the {@code service} is null, then Duration.ZERO is returned. If the period is 0 then 194 * the result of this method will simply be {@code currentFailureCount}. In all other cases, 195 * the returned value is the same as {@code period + (period * currentFailureCount)}.</p> 196 */ 197 public static final Callback<ScheduledService<?>, Duration> LINEAR_BACKOFF_STRATEGY 198 = new Callback<ScheduledService<?>, Duration>() { 199 @Override public Duration call(ScheduledService<?> service) { 200 if (service == null) return Duration.ZERO; 201 final double period = service.getPeriod() == null ? 0 : service.getPeriod().toMillis(); 202 final double x = service.getCurrentFailureCount(); 203 return Duration.millis(period == 0 ? x : period + (period * x)); 204 } 205 }; 206 207 /** 208 * This Timer is used to schedule the delays for each ScheduledService. A single timer 209 * ought to be able to easily service thousands of ScheduledService objects. 210 */ 211 private static final Timer DELAY_TIMER = new Timer("ScheduledService Delay Timer", true); 212 213 /** 214 * The initial delay between when the ScheduledService is first started, and when it will begin 215 * operation. This is the amount of time the ScheduledService will remain in the SCHEDULED state, 216 * before entering the RUNNING state, following a fresh invocation of {@link #start()} or {@link #restart()}. 217 */ 218 private ObjectProperty<Duration> delay = new SimpleObjectProperty<>(this, "delay", Duration.ZERO); 219 public final Duration getDelay() { return delay.get(); } 220 public final void setDelay(Duration value) { delay.set(value); } 221 public final ObjectProperty<Duration> delayProperty() { return delay; } 222 223 /** 224 * The minimum amount of time to allow between the start of the last run and the start of the next run. 225 * The actual period (also known as <code>cumulativePeriod</code>) 226 * will depend on this property as well as the <code>backoffStrategy</code> and number of failures. 227 */ 228 private ObjectProperty<Duration> period = new SimpleObjectProperty<>(this, "period", Duration.ZERO); 229 public final Duration getPeriod() { return period.get(); } 230 public final void setPeriod(Duration value) { period.set(value); } 231 public final ObjectProperty<Duration> periodProperty() { return period; } 232 233 /** 234 * Computes the amount of time to add to the period on each failure. This cumulative amount is reset whenever 235 * the the ScheduledService is manually restarted. 236 */ 237 private ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategy = 238 new SimpleObjectProperty<>(this, "backoffStrategy", LOGARITHMIC_BACKOFF_STRATEGY); 239 public final Callback<ScheduledService<?>,Duration> getBackoffStrategy() { return backoffStrategy.get(); } 240 public final void setBackoffStrategy(Callback<ScheduledService<?>, Duration> value) { backoffStrategy.set(value); } 241 public final ObjectProperty<Callback<ScheduledService<?>,Duration>> backoffStrategyProperty() { return backoffStrategy; } 242 243 /** 244 * Indicates whether the ScheduledService should automatically restart in the case of a failure in the Task. 245 */ 246 private BooleanProperty restartOnFailure = new SimpleBooleanProperty(this, "restartOnFailure", true); 247 public final boolean getRestartOnFailure() { return restartOnFailure.get(); } 248 public final void setRestartOnFailure(boolean value) { restartOnFailure.set(value); } 249 public final BooleanProperty restartOnFailureProperty() { return restartOnFailure; } 250 251 /** 252 * The maximum number of times the ScheduledService can fail before it simply ends in the FAILED 253 * state. You can of course restart the ScheduledService manually, which will cause the current 254 * count to be reset. 255 */ 256 private IntegerProperty maximumFailureCount = new SimpleIntegerProperty(this, "maximumFailureCount", Integer.MAX_VALUE); 257 public final int getMaximumFailureCount() { return maximumFailureCount.get(); } 258 public final void setMaximumFailureCount(int value) { maximumFailureCount.set(value); } 259 public final IntegerProperty maximumFailureCountProperty() { return maximumFailureCount; } 260 261 /** 262 * The current number of times the ScheduledService has failed. This is reset whenever the 263 * ScheduledService is manually restarted. 264 */ 265 private ReadOnlyIntegerWrapper currentFailureCount = new ReadOnlyIntegerWrapper(this, "currentFailureCount", 0); 266 public final int getCurrentFailureCount() { return currentFailureCount.get(); } 267 public final ReadOnlyIntegerProperty currentFailureCountProperty() { return currentFailureCount.getReadOnlyProperty(); } 268 private void setCurrentFailureCount(int value) { 269 currentFailureCount.set(value); 270 } 271 272 /** 273 * The current cumulative period in use between iterations. This will be the same as <code>period</code>, 274 * except after a failure, in which case the result of the backoffStrategy will be used as the cumulative period 275 * following each failure. This is reset whenever the ScheduledService is manually restarted or an iteration 276 * is successful. The cumulativePeriod is modified when the ScheduledService enters the scheduled state. 277 * The cumulativePeriod can be capped by setting the {@code maximumCumulativePeriod}. 278 */ 279 private ReadOnlyObjectWrapper<Duration> cumulativePeriod = new ReadOnlyObjectWrapper<>(this, "cumulativePeriod", Duration.ZERO); 280 public final Duration getCumulativePeriod() { return cumulativePeriod.get(); } 281 public final ReadOnlyObjectProperty<Duration> cumulativePeriodProperty() { return cumulativePeriod.getReadOnlyProperty(); } 282 void setCumulativePeriod(Duration value) { // package private for testing 283 // Make sure any null value is turned into ZERO 284 Duration newValue = value == null || value.toMillis() < 0 ? Duration.ZERO : value; 285 // Cap the newValue based on the maximumCumulativePeriod. 286 Duration maxPeriod = maximumCumulativePeriod.get(); 287 if (maxPeriod != null && !maxPeriod.isUnknown() && !newValue.isUnknown()) { 288 if (maxPeriod.toMillis() < 0) { 289 newValue = Duration.ZERO; 290 } else if (!maxPeriod.isIndefinite() && newValue.greaterThan(maxPeriod)) { 291 newValue = maxPeriod; 292 } 293 } 294 cumulativePeriod.set(newValue); 295 } 296 297 /** 298 * The maximum allowed value for the cumulativePeriod. Setting this value will help ensure that in the case of 299 * repeated failures the back-off algorithm doesn't end up producing unreasonably large values for 300 * cumulative period. The cumulative period is guaranteed not to be any larger than this value. If the 301 * maximumCumulativePeriod is negative, then cumulativePeriod will be capped at 0. If maximumCumulativePeriod 302 * is NaN or null, then it will not influence the cumulativePeriod. 303 */ 304 private ObjectProperty<Duration> maximumCumulativePeriod = new SimpleObjectProperty<>(this, "maximumCumulativePeriod", Duration.INDEFINITE); 305 public final Duration getMaximumCumulativePeriod() { return maximumCumulativePeriod.get(); } 306 public final void setMaximumCumulativePeriod(Duration value) { maximumCumulativePeriod.set(value); } 307 public final ObjectProperty<Duration> maximumCumulativePeriodProperty() { return maximumCumulativePeriod; } 308 309 /** 310 * The last successfully computed value. During each iteration, the "value" of the ScheduledService will be 311 * reset to null, as with any other Service. The "lastValue" however will be set to the most recently 312 * successfully computed value, even across iterations. It is reset however whenever you manually call 313 * reset or restart. 314 */ 315 private ReadOnlyObjectWrapper<V> lastValue = new ReadOnlyObjectWrapper<>(this, "lastValue", null); 316 public final V getLastValue() { return lastValue.get(); } 317 public final ReadOnlyObjectProperty<V> lastValueProperty() { return lastValue.getReadOnlyProperty(); } 318 319 /** 320 * The timestamp of the last time the task was run. This is used to compute the amount 321 * of delay between successive iterations by taking the cumulativePeriod into account. 322 */ 323 private long lastRunTime = 0L; 324 325 /** 326 * Whether or not this iteration is a "fresh start", such as the initial call to start, 327 * or a call to restart, or a call to reset followed by a call to start. 328 */ 329 private boolean freshStart = true; 330 331 /** 332 * This is a TimerTask scheduled with the DELAY_TIMER. All it does is kick off the execution 333 * of the actual background Task. 334 */ 335 private TimerTask delayTask = null; 336 337 /** 338 * This is set to false when the "cancel" method is called, and reset to true on "reset". 339 * We need this so that any time the developer calls 'cancel', even when from within one 340 * of the event handlers, it will cause us to transition to the cancelled state. 341 */ 342 private boolean stop = false; 343 344 // This method is invoked by Service to actually execute the task. In the normal implementation 345 // in Service, this method will simply delegate to the Executor. In ScheduledService, however, 346 // we instead will delay the correct amount of time before we finally invoke executeTaskNow, 347 // which is where we end up delegating to the executor. 348 @Override protected void executeTask(final Task<V> task) { 349 assert task != null; 350 checkThread(); 351 352 if (freshStart) { 353 // The delayTask should have concluded and been made null by this point. 354 // If not, then somehow we were paused waiting for another iteration and 355 // somebody caused the system to run again. However resetting things should 356 // have cleared the delayTask. 357 assert delayTask == null; 358 359 // The cumulativePeriod needs to be initialized 360 setCumulativePeriod(getPeriod()); 361 362 // Pause for the "delay" amount of time and then execute 363 final long d = (long) normalize(getDelay()); 364 if (d == 0) { 365 // If the delay is zero or null, then just start immediately 366 executeTaskNow(task); 367 } else { 368 schedule(delayTask = createTimerTask(task), d); 369 } 370 } else { 371 // We are executing as a result of an iteration, not a fresh start. 372 // If the runPeriod (time between the last run and now) exceeds the cumulativePeriod, then 373 // we need to execute immediately. Otherwise, we will pause until the cumulativePeriod has 374 // been reached, and then run. 375 double cumulative = normalize(getCumulativePeriod()); // Can never be null. 376 double runPeriod = clock() - lastRunTime; 377 if (runPeriod < cumulative) { 378 // Pause and then execute 379 assert delayTask == null; 380 schedule(delayTask = createTimerTask(task), (long) (cumulative - runPeriod)); 381 } else { 382 // Execute immediately 383 executeTaskNow(task); 384 } 385 } 386 } 387 388 /** 389 * {@inheritDoc} 390 * 391 * Implementation Note: Subclasses which override this method must call this super implementation. 392 */ 393 @Override protected void succeeded() { 394 super.succeeded(); 395 lastValue.set(getValue()); 396 // Reset the cumulative time 397 Duration d = getPeriod(); 398 setCumulativePeriod(d); 399 // Have to save this off, since it will be reset here in a second 400 final boolean wasCancelled = stop; 401 // Call the super implementation of reset, which will not cause us 402 // to think this is a new fresh start. 403 superReset(); 404 assert freshStart == false; 405 // If it was cancelled then we will progress from READY to SCHEDULED to CANCELLED so that 406 // the lifecycle changes are predictable according to the Service specification. 407 if (wasCancelled) { 408 cancelFromReadyState(); 409 } else { 410 // Fire it up! 411 start(); 412 } 413 } 414 415 /** 416 * {@inheritDoc} 417 * 418 * Implementation Note: Subclasses which override this method must call this super implementation. 419 */ 420 @Override protected void failed() { 421 super.failed(); 422 assert delayTask == null; 423 // Restart as necessary 424 setCurrentFailureCount(getCurrentFailureCount() + 1); 425 if (getRestartOnFailure() && getMaximumFailureCount() > getCurrentFailureCount()) { 426 // We've not yet maxed out the number of failures we can 427 // encounter, so we're going to iterate 428 Callback<ScheduledService<?>,Duration> func = getBackoffStrategy(); 429 if (func != null) { 430 Duration d = func.call(this); 431 setCumulativePeriod(d); 432 } 433 434 superReset(); 435 assert freshStart == false; 436 start(); 437 } else { 438 // We've maxed out, so do nothing and things will just stop. 439 } 440 } 441 442 /** 443 * {@inheritDoc} 444 * 445 * Implementation Note: Subclasses which override this method must call this super implementation. 446 */ 447 @Override public void reset() { 448 super.reset(); 449 stop = false; 450 setCumulativePeriod(getPeriod()); 451 lastValue.set(null); 452 setCurrentFailureCount(0); 453 lastRunTime = 0L; 454 freshStart = true; 455 } 456 457 /** 458 * Cancels any currently running task and stops this scheduled service, such that 459 * no additional iterations will occur. 460 * 461 * @return whether any running task was cancelled, false if no task was cancelled. 462 * In any case, the ScheduledService will stop iterating. 463 */ 464 @Override public boolean cancel() { 465 boolean ret = super.cancel(); 466 stop = true; 467 if (delayTask != null) { 468 delayTask.cancel(); 469 delayTask = null; 470 } 471 return ret; 472 } 473 474 /** 475 * This method exists only for testing purposes. The normal implementation 476 * will delegate to a java.util.Timer, however during testing we want to simply 477 * inspect the value for the delay and execute immediately. 478 * @param task not null 479 * @param delay >= 0 480 */ 481 void schedule(TimerTask task, long delay) { 482 DELAY_TIMER.schedule(task, delay); 483 } 484 485 /** 486 * This method only exists for the sake of testing. 487 * @return freshStart 488 */ 489 boolean isFreshStart() { return freshStart; } 490 491 /** 492 * Gets the time of the current clock. At runtime this is simply getting the results 493 * of System.currentTimeMillis, however during testing this is hammered so as to return 494 * a time that works well during testing. 495 * @return The clock time 496 */ 497 long clock() { 498 return System.currentTimeMillis(); 499 } 500 501 /** 502 * Called by this class when we need to avoid calling this class' implementation of 503 * reset which has the side effect of resetting the "freshStart", currentFailureCount, 504 * and other state. 505 */ 506 private void superReset() { 507 super.reset(); 508 } 509 510 /** 511 * Creates the TimerTask used for delaying execution. The delay can either be due to 512 * the initial delay (if this is a freshStart), or it can be the computed delay in order 513 * to execute the task on its fixed schedule. 514 * 515 * @param task must not be null. 516 * @return the delay TimerTask. 517 */ 518 private TimerTask createTimerTask(final Task<V> task) { 519 assert task != null; 520 return new TimerTask() { 521 @Override public void run() { 522 Runnable r = () -> { 523 executeTaskNow(task); 524 delayTask = null; 525 }; 526 527 // We must make sure that executeTaskNow is called from the FX thread. 528 // This must happen on th FX thread because the super implementation of 529 // executeTask is going to call getExecutor so it can use any user supplied 530 // executor, and this property can only be read on the FX thread. 531 if (isFxApplicationThread()) { 532 r.run(); 533 } else { 534 runLater(r); 535 } 536 } 537 }; 538 } 539 540 /** 541 * Called when it is time to actually execute the task (any delay has by now been 542 * accounted for). Essentially this ends up simply calling the super implementation 543 * of executeTask and doing some bookkeeping. 544 * 545 * @param task must not be null 546 */ 547 private void executeTaskNow(Task<V> task) { 548 assert task != null; 549 lastRunTime = clock(); 550 freshStart = false; 551 super.executeTask(task); 552 } 553 554 /** 555 * Normalize our handling of Durations according to the class documentation. 556 * @param d can be null 557 * @return a double representing the millis. 558 */ 559 private static double normalize(Duration d) { 560 if (d == null || d.isUnknown()) return 0; 561 if (d.isIndefinite()) return Double.MAX_VALUE; 562 return d.toMillis(); 563 } 564 }