changeset: 229:9ee4de8ee2ce
tag: qtip
tag: scheduled
tag: tip
user: rbair
date: Mon Jan 09 15:09:23 2012 -0800
summary: [mq]: scheduled
diff -r 160b675b10e3 -r 9ee4de8ee2ce javafx-concurrent/src/javafx/concurrent/ObservableListTask.java
--- a/javafx-concurrent/src/javafx/concurrent/ObservableListTask.java Mon Jan 09 14:21:59 2012 -0800
+++ b/javafx-concurrent/src/javafx/concurrent/ObservableListTask.java Mon Jan 09 15:09:23 2012 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
diff -r 160b675b10e3 -r 9ee4de8ee2ce javafx-concurrent/src/javafx/concurrent/ScheduledService.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-concurrent/src/javafx/concurrent/ScheduledService.java Mon Jan 09 15:09:23 2012 -0800
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.concurrent;
+
+import javafx.animation.PauseTransition;
+import javafx.beans.property.*;
+import javafx.event.ActionEvent;
+import javafx.event.EventHandler;
+import javafx.util.Callback;
+import javafx.util.Duration;
+
+/**
+ *
The ScheduledService is a service which will automatically restart
+ * itself after a successful execution, and under some conditions will
+ * restart even in case of failure. A new ScheduledService begins in
+ * the READY state, just as a normal Service. After calling
+ * start
or restart
, the ScheduledService will
+ * enter the SCHEDULED state for the duration specified by delay
.
+ *
+ *
+ * Once RUNNING, the ScheduledService will execute its Task. On successful
+ * completion, the ScheduledService will transition to the SUCCEEDED state,
+ * and then to the READY state and back to the SCHEDULED state. The amount
+ * of time the ScheduledService will remain in this state depends on the
+ * amount of time between the last state transition to RUNNING, and the
+ * current time, and the period
. In short, the period
+ * defines the minimum amount of time between executions. If the previous
+ * execution completed before period
expires, then the
+ * ScheduledService will remain in the SCHEDULED state until the period
+ * expires. If on the other hand the execution took longer than the
+ * specified period, then the ScheduledService will immediately transition
+ * back to RUNNING.
+ *
+ * If, while RUNNING, the ScheduledService's Task throws an error or in
+ * some other way ends up transitioning to FAILED, then the ScheduledService
+ * will either restart or quit, depending on the values for
+ * computeEaseOff
, restartOnFailure
, and
+ * maximumFailureCount
.
+ *
+ * If a failure occurs and restartOnFailure
is false, then
+ * the ScheduledService will transition to FAILED and will stop. To restart
+ * a failed ScheduledService, you must call restart manually.
+ *
+ * If a failure occurs and restartOnFailure
is true, then
+ * the the ScheduledService may restart automatically. First,
+ * the result of calling computeEaseOff
will become the
+ * new cumulativePeriod
. In this way, after each failure, you can cause
+ * the service to wait a longer and longer period of time before restarting.
+ * ScheduledService defines static EXPONENTIAL_EASE_OFF and LOGARITHMIC_EASE_OFF
+ * implementations, of which LOGARITHMIC_EASE_OFF is the default value of
+ * computeEaseOff. After maximumFailureCount
is reached, the
+ * ScheduledService will transition to FAILED in exactly the same way as if
+ * restartOnFailure
were false.
+ */
+public abstract class ScheduledService extends Service {
+ /**
+ * A Callback implementation for the computeEaseOff
property which
+ * will exponentially ease off the period between re-executions in the case of
+ * a failure. This computation takes the original period and the number of
+ * consecutive failures and computes the ease off amount from that information.
+ */
+ public static final Callback, Duration> EXPONENTIAL_EASE_OFF
+ = new Callback, Duration>() {
+ @Override public Duration call(ScheduledService> service) {
+ double period = (service == null || service.getPeriod() == null) ? 0 : service.getPeriod().toMillis();
+ double x = (service == null) ? 0 : service.getCurrentFailureCount();
+ return Duration.millis(period + (period * Math.exp(x)));
+ }
+ };
+
+ /**
+ * A Callback implementation for the computeEaseOff
property which
+ * will logarithmically ease off the period between re-executions in the case of
+ * a failure. This computation takes the original period and the number of
+ * consecutive failures and computes the ease off amount from that information.
+ */
+ public static final Callback, Duration> LOGARITHMIC_EASE_OFF
+ = new Callback, Duration>() {
+ @Override public Duration call(ScheduledService> service) {
+ double period = (service == null || service.getPeriod() == null) ? 0 : service.getPeriod().toMillis();
+ double x = (service == null) ? 0 : service.getCurrentFailureCount();
+ return Duration.millis(period + (period * Math.log1p(x)));
+ }
+ };
+
+ /**
+ * The initial delay between when the ScheduledService is first started, and when it will begin
+ * operation. This is the amount of time the ScheduledService will remain in the SCHEDULED state,
+ * before entering the RUNNING state.
+ */
+ private ObjectProperty delay = new SimpleObjectProperty(this, "delay", Duration.ZERO);
+ public final Duration getDelay() { return delay.get(); }
+ public final void setDelay(Duration value) { delay.set(value); }
+ public final ObjectProperty delayProperty() { return delay; }
+
+ /**
+ * The minimum amount of time to allow between the last time the service was in the RUNNING state
+ * until it should run again. The actual period (also known as cumulativePeriod
)
+ * will depend on this property as well as the computeEaseOff
and number of failures.
+ */
+ private ObjectProperty period = new SimpleObjectProperty(this, "period", Duration.ZERO);
+ public final Duration getPeriod() { return period.get(); }
+ public final void setPeriod(Duration value) { period.set(value); }
+ public final ObjectProperty periodProperty() { return period; }
+
+ /**
+ * Computes the amount of time to add to the period on each failure. This cumulative amount is reset whenever
+ * the the ScheduledService is manually restarted. The Callback takes a Duration, which is the last
+ * cumulativePeriod
, and returns a Duration which will be the new cumulativePeriod
.
+ */
+ private ObjectProperty,Duration>> computeEaseOff =
+ new SimpleObjectProperty,Duration>>(this, "computeEaseOff", LOGARITHMIC_EASE_OFF);
+ public final Callback,Duration> getComputeEaseOff() { return computeEaseOff.get(); }
+ public final void setComputeEaseOff(Callback,Duration> value) { computeEaseOff.set(value); }
+ public final ObjectProperty,Duration>> computeEaseOffProperty() { return computeEaseOff; }
+
+ /**
+ * Indicates whether the ScheduledService should automatically restart in the case of a failure.
+ */
+ private BooleanProperty restartOnFailure = new SimpleBooleanProperty(this, "restartOnFailure", false);
+ public final boolean getRestartOnFailure() { return restartOnFailure.get(); }
+ public final void setRestartOnFailure(boolean value) { restartOnFailure.set(value); }
+ public final BooleanProperty restartOnFailureProperty() { return restartOnFailure; }
+
+ /**
+ * The maximum number of times the ScheduledService can fail before it simply ends in the FAILED
+ * state. You can of course restart the ScheduledService manually, which will cause the current
+ * count to be reset.
+ */
+ private IntegerProperty maximumFailureCount = new SimpleIntegerProperty(this, "maximumFailureCount", Integer.MAX_VALUE);
+ public final int getMaximumFailureCount() { return maximumFailureCount.get(); }
+ public final void setMaximumFailureCount(int value) { maximumFailureCount.set(value); }
+ public final IntegerProperty maximumFailureCountProperty() { return maximumFailureCount; }
+
+ /**
+ * The current number of times the ScheduledService has failed. This is reset whenever the
+ * ScheduledService is manually restarted.
+ */
+ private ReadOnlyIntegerWrapper currentFailureCount = new ReadOnlyIntegerWrapper(this, "currentFailureCount", 0);
+ public final int getCurrentFailureCount() { return currentFailureCount.get(); }
+ public final ReadOnlyIntegerProperty currentFailureCountProperty() { return currentFailureCount.getReadOnlyProperty(); }
+
+ /**
+ * The current cumulative period in use between iterations. This will be the same as period
,
+ * except after a failure, in which case the easeOffDuration
will be added to the period
+ * for each failure when. This is reset whenever the ScheduledService is manually restarted.
+ */
+ private ReadOnlyObjectWrapper cumulativePeriod = new ReadOnlyObjectWrapper(this, "cumulativePeriod", Duration.ZERO);
+ public final Duration getCumulativePeriod() { return cumulativePeriod.get(); }
+ public final ReadOnlyObjectProperty cumulativePeriodProperty() { return cumulativePeriod.getReadOnlyProperty(); }
+
+ /**
+ * The last successfully computed value. During each iteration, the "value" of the ScheduledService will be
+ * reset to null, as with any other Service. The "lastValue" however will be set to the most currently
+ * successfully computed value, even across iterations. It is reset however whenever you manually call
+ * reset or restart.
+ */
+ private ReadOnlyObjectWrapper lastValue = new ReadOnlyObjectWrapper(this, "lastValue", null);
+ public final V getLastValue() { return lastValue.get(); }
+ public final ReadOnlyObjectProperty lastValueProperty() { return lastValue.getReadOnlyProperty(); }
+
+ /**
+ * The timestamp of the last time the thing was run
+ */
+ private long lastRunTime = 0L;
+ private boolean freshStart = true;
+ // private boolean performIteration = false;
+ private PauseTransition pauseTransition = null;
+
+ @Override protected void succeeded() {
+ super.succeeded();
+ lastValue.set(getValue());
+ // Reset the cumulative time
+ Duration d = getPeriod();
+ setCumulativePeriod(d == null ? Duration.ZERO : d);
+ // Call the super implementation of reset, which will not cause us
+ // to think this is a new fresh start.
+ ScheduledService.this.superReset();
+ // Fire it up!
+ ScheduledService.this.start();
+ }
+
+ @Override protected void cancelled() {
+ super.cancelled();
+ // Stop the pauseTransition if it exists
+ if (pauseTransition != null) {
+ pauseTransition.stop();
+ pauseTransition = null;
+ }
+ }
+
+ @Override protected void failed() {
+ super.failed();
+ // Stop the pauseTransition if it exists
+ if (pauseTransition != null) {
+ pauseTransition.stop();
+ pauseTransition = null;
+ }
+ // Restart as necessary
+ setCurrentFailureCount(getCurrentFailureCount() + 1);
+ if (getMaximumFailureCount() > getCurrentFailureCount()) {
+ // We've not yet maxed out the number of failures we can
+ // encounter, so we're going to iterate
+
+ Callback,Duration> func = getComputeEaseOff();
+ if (func != null) {
+ Duration d = func.call(this);
+ setCumulativePeriod(d == null ? Duration.ZERO : d);
+ }
+
+ ScheduledService.this.superReset();
+ // Fire it up!
+ ScheduledService.this.start();
+ } else {
+ // We've maxed out, so do nothing and things will just stop.
+ }
+ }
+
+ private void superReset() {
+ super.reset();
+ }
+
+ @Override public void reset() {
+ super.reset();
+ Duration p = getPeriod();
+ setCumulativePeriod(p == null ? Duration.ZERO : p);
+ lastValue.set(null);
+ currentFailureCount.set(0);
+ lastRunTime = 0L;
+ freshStart = true;
+ }
+
+ @Override protected void executeTask(final Task task) {
+ checkThread();
+ if (freshStart) {
+ // The pauseTransition should have concluded and been made null by this point.
+ // If not, then somehow we were paused waiting for another iteration and
+ // somebody caused the system to run again. However resetting things should
+ // have cleared the transition.
+ assert pauseTransition == null;
+
+ // Pause for the "delay" amount of time then execute
+ final Duration d = getDelay();
+ if (d == null || d.toMillis() == 0) {
+ // If the delay is zero or null, then just start immediately
+ executeTask(task);
+ } else {
+ pauseTransition = new PauseTransition(d == null ? Duration.ZERO : d);
+ pauseTransition.setOnFinished(new EventHandler() {
+ @Override public void handle(ActionEvent actionEvent) {
+ executeTaskNow(task);
+ pauseTransition = null;
+ }
+ });
+ pauseTransition.play();
+ }
+ } else {
+ // We are executing as a result of an iteration, not a fresh start.
+ // If the runPeriod (time between the last run and now) exceeds the cumulativePeriod, then
+ // we need to execute immediately. Otherwise, we will pause until the cumulativePeriod has
+ // been reached, and then run.
+ double cumulative = getCumulativePeriod().toMillis(); // Can never be null.
+ double runPeriod = System.currentTimeMillis() - lastRunTime;
+ if (runPeriod < cumulative) {
+ // Pause and then execute
+ assert pauseTransition == null;
+ pauseTransition = new PauseTransition(Duration.millis(cumulative - runPeriod));
+ pauseTransition.setOnFinished(new EventHandler() {
+ @Override public void handle(ActionEvent actionEvent) {
+ executeTaskNow(task);
+ pauseTransition = null;
+ }
+ });
+ pauseTransition.play();
+ } else {
+ // Execute immediately
+ executeTaskNow(task);
+ }
+ }
+ }
+
+ private void executeTaskNow(Task task) {
+ lastRunTime = System.currentTimeMillis();
+ freshStart = false;
+ super.executeTask(task);
+ }
+
+ void setCumulativePeriod(Duration value) {
+ if (value == null) throw new NullPointerException("cumulative period cannot be null");
+ cumulativePeriod.set(value);
+ }
+
+ void setCurrentFailureCount(int value) {
+ currentFailureCount.set(value);
+ }
+}
\ No newline at end of file
diff -r 160b675b10e3 -r 9ee4de8ee2ce javafx-concurrent/src/javafx/concurrent/TaskBase.java
--- a/javafx-concurrent/src/javafx/concurrent/TaskBase.java Mon Jan 09 14:21:59 2012 -0800
+++ b/javafx-concurrent/src/javafx/concurrent/TaskBase.java Mon Jan 09 15:09:23 2012 -0800
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it