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