[REVIEW] Auto-restartable ScheduledService implementation
Richard Bair
richard.bair at oracle.com
Fri Dec 23 16:26:00 PST 2011
Wow, and very satisfying to see that the required change to Service to support this class is now in the openjfx rt repo. So go update, rebuild, and use the file attached to this issue to give it a spin.
Richard
On Dec 23, 2011, at 4:10 PM, Richard Bair wrote:
> Hi,
>
> http://javafx-jira.kenai.com/browse/RT-18702
>
> While waiting for builds and such, I implemented a ScheduledService. Several folks on the forums had asked for something like this, so I thought I'd take a shot at writing a complete implementation that could make it into 2.1. I would end up writing all the unit tests for this, and total development time is estimated at no more than 4 days (so far I've got 4 hours put into it and it isn't in too shabby of condition, so I think the estimate is reasonable).
>
> Attached to the issue is the implementation. I'd love if somebody would like to take a look, particularly at the API. The name comes from the java.util.concurrent package, which has a ScheduledExecutorService. Since this is performing a similar function, a similar name seemed in order. In addition, the "period" name comes from that package as well (since the names used in the animation package I think have different semantics, so using a different name is a good idea here, I think).
>
> Here it is:
>
> /**
> * <p>
> * 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
> * <code>start</code> or <code>restart</code>, the ScheduledService will
> * enter the SCHEDULED state for the duration specified by <code>delay</code>.
> * </p>
> * <p>
> * 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 <code>period</code>. In short, the <code>period</code>
> * defines the minimum amount of time between executions. If the previous
> * execution completed before <code>period</code> 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.
> * </p>
> * <p>
> * 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
> * <code>computeEaseOff</code>, <code>restartOnFailure</code>, and
> * <code>maximumFailureCount</code>.
> * </p>
> * <p>
> * If a failure occurs and <code>restartOnFailure</code> is false, then
> * the ScheduledService will transition to FAILED and will stop. To restart
> * a failed ScheduledService, you must call restart manually.
> * </p>
> * <p>
> * If a failure occurs and <code>restartOnFailure</code> is true, then
> * the the ScheduledService <em>may</em> restart automatically. First,
> * the result of calling <code>computeEaseOff</code> will become the
> * new <code>cumulativePeriod</code>. 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 <code>maximumFailureCount</code> is reached, the
> * ScheduledService will transition to FAILED in exactly the same way as if
> * <code>restartOnFailure</code> were false.
> * </p>
> */
>
> /**
> * A Callback implementation for the <code>computeEaseOff</code> 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<ScheduledService<?>, Duration> EXPONENTIAL_EASE_OFF....
>
> /**
> * A Callback implementation for the <code>computeEaseOff</code> 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<ScheduledService<?>, Duration> LOGARITHMIC_EASE_OFF....
>
> /**
> * 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<Duration> delay = new SimpleObjectProperty<Duration>(this, "delay", Duration.ZERO);
> ....
>
> /**
> * 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 <code>cumulativePeriod</code>)
> * will depend on this property as well as the <code>computeEaseOff</code> and number of failures.
> */
> private ObjectProperty<Duration> period = new SimpleObjectProperty<Duration>(this, "period", Duration.ZERO);
> ....
>
> /**
> * 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
> * <code>cumulativePeriod</code>, and returns a Duration which will be the new <code>cumulativePeriod</code>.
> */
> private ObjectProperty<Callback<ScheduledService<?>,Duration>> computeEaseOff =
> new SimpleObjectProperty<Callback<ScheduledService<?>,Duration>>(this, "computeEaseOff", LOGARITHMIC_EASE_OFF);
> ....
>
> /**
> * Indicates whether the ScheduledService should automatically restart in the case of a failure.
> */
> private BooleanProperty restartOnFailure = new SimpleBooleanProperty(this, "restartOnFailure", false);
> ....
>
> /**
> * 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);
> ....
>
> /**
> * 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);
> ....
>
> /**
> * The current cumulative period in use between iterations. This will be the same as <code>period</code>,
> * except after a failure, in which case the <code>computeEaseOff</code> will compute a new period. This
> * is reset whenever the ScheduledService is manually restarted.
> */
> private ReadOnlyObjectWrapper<Duration> cumulativePeriod = new ReadOnlyObjectWrapper<Duration>(this, "cumulativePeriod", Duration.ZERO);
> ....
>
> /**
> * 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<V> lastValue = new ReadOnlyObjectWrapper<V>(this, "lastValue", null);
> ....
>
>
> It seems pretty powerful and flexible but the API isn't too bad. I played with a couple configurations but this one seemed to play best with the existing semantics of the Service. For example, on Service the "value" is set to null whenever the Service is reset for another run. But this doesn't work as well for an auto-restarting service like ScheduledService. So rather than break the semantic that "value" is set to null on restart, I added a new variable "lastValue" which remembers the last value even across failures, and is only rest when the developer manually resets or restarts things. During an auto-restart iteration, the lastValue is not changed (it is only updated to the last "value" of a successful run).
>
> Thanks
> Richard
More information about the openjfx-dev
mailing list