/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.thread;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.jini.constants.TimeConstants;
/**
* An abstract class for building tasks that retry on failure after a
* timeout period. This builds upon <code>TaskManager</code> for task
* execution and <code>WakeupManager</code> for retry scheduling. You
* extend <code>RetryTask</code>, implementing <code>tryOnce</code> to
* represent a single attempt at the task. If <code>tryOnce</code>
* returns <code>true</code>, the attempt was successful and the task
* is complete. If <code>tryOnce</code> returns <code>false</code>,
* the task is scheduled for a future retry using <code>WakeupManager</code>.
* <p>
* The default retry times are defined by this class's implementation
* of <code>retryTime</code></code>. You can override this method to
* change the retry times.
* <p>
* It is legal to reuse the same task again and again, but only after
* the task is complete and <code>reset</code> has been called.
* Inserting the same task multiple times before it has either been
* cancelled or completed successfully will generate unpredictable
* behavior.<p>
*
* This class uses the {@link Logger} named
* <code>com.sun.jini.thread.RetryTask</code> to log information at
* the following logging levels: <p>
*
* <table border=1 cellpadding=5
* summary="Describes logging performed by RetryTask at different
* logging levels">
*
* <tr> <th> Level <th> Description
*
* <tr> <td> FINEST <td> after a failed attempt, when should
* the task be scheduled for a re-try
*
* </table>
*
* @author Sun Microsystems, Inc.
*
* @see TaskManager
* @see WakeupManager
*/
import com.sun.jini.thread.WakeupManager.Ticket;
public abstract class RetryTask implements TaskManager.Task, TimeConstants {
private TaskManager manager; // the TaskManager for this task
private RetryTime retry; // the retry object for this task
private boolean cancelled; // have we been cancelled?
private boolean complete; // have we completed successfully?
private Ticket ticket; // the WakeupManager ticket
private long startTime; // the time when we were created or
// last reset
private int attempt; // the current attempt number
private WakeupManager wakeup; // WakeupManager for retry scheduling
/**
* Default delay backoff times. These are converted from
* intervals to "time since start" by the static block below.
*
* @see #retryTime
*/
private static final long[] delays = {
0, // First value is never read
1 * SECONDS,
5 * SECONDS,
10 * SECONDS,
1 * MINUTES,
1 * MINUTES,
5 * MINUTES,
};
/** Logger for this class */
private static final Logger logger =
Logger.getLogger("com.sun.jini.thread.RetryTask");
/**
* Create a new <code>RetryTask</code> that will be scheduled with
* the given task manager, and which will perform retry scheduling
* using the given wakeup manager.
*/
public RetryTask(TaskManager manager, WakeupManager wakeupManager) {
this.manager = manager;
this.wakeup = wakeupManager;
reset();
}
/**
* Make a single attempt. Return <code>true</code> if the attempt
* was successful. If the attempt is not successful, the task
* will be scheduled for a future retry.
*/
public abstract boolean tryOnce();
/**
* The <code>run</code> method used as a
* <code>TaskManager.Task</code>. This invokes
* <code>tryOnce</code>. If it is not successful, it schedules
* the task for a future retry at the time it gets by invoking
* <code>retryTime</code>.
*
* @see #tryOnce
* @see #startTime
*/
public void run() {
synchronized (this) { // avoid retry if cancelled
if (cancelled) // if they cancelled
return; // do nothing
}
boolean success = tryOnce();
synchronized (this) {
if (!success) { // if at first we don't succeed ...
attempt++;
long at = retryTime(); // ... try, try again
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "retry of {0} in {1} ms",
new Object[]{this,
new Long(at - System.currentTimeMillis())});
}
if (retry == null) // only create it if we need to
retry = new RetryTime();
ticket = wakeup.schedule(at, retry);
} else {
complete = true;
notifyAll(); // see waitFor()
}
}
}
/**
* Return the next time at which we should make another attempt.
* This is <em>not</em> an interval, but the actual time.
* <p>
* The implementation is free to do as it pleases with the policy
* here. The default implementation is to delay using intervals of
* 1 second, 5 seconds, 10 seconds, 1 minute, and 1 minute between
* attempts, and then retrying every five minutes forever.
* <p>
* The default implementation assumes it is being called from
* the default <code>run</code> method and that the current thread
* holds the lock on this object. If the caller does
* not own the lock the result is undefined and could result in an
* exception.
*/
public long retryTime() {
assert Thread.holdsLock(this): "thread does not hold lock";
int index = (attempt < delays.length ? attempt : delays.length - 1);
return delays[index] + System.currentTimeMillis();
}
/**
* Return the time this task was created, or the last
* time {@link #reset reset} was called.
*/
public synchronized long startTime() {
return startTime;
}
/**
* Return the attempt number, starting with zero.
*/
public synchronized int attempt() {
return attempt;
}
/**
* Cancel the retrying of the task. This ensures that there will be
* no further attempts to invoke <code>tryOnce</code>. It will not
* interfere with any ongoing invocation of <code>tryOnce</code>
* unless a subclass overrides this to do so. Any override of this
* method should invoke <code>super.cancel()</code>.
*/
public synchronized void cancel() {
cancelled = true;
if (ticket != null)
wakeup.cancel(ticket);
notifyAll(); // see waitFor()
}
/**
* Return <code>true</code> if <code>cancel</code> has been invoked.
*/
public synchronized boolean cancelled() {
return cancelled;
}
/**
* Return <code>true</code> if <code>tryOnce</code> has returned
* successfully.
*/
public synchronized boolean complete() {
return complete;
}
public synchronized boolean waitFor() throws InterruptedException {
while (!cancelled && !complete)
wait();
return complete;
}
/**
* Reset values for a new use of this task.
*/
public synchronized void reset() {
cancel(); // remove from the wakeup queue
startTime = System.currentTimeMillis();
cancelled = false;
complete = false;
ticket = null;
attempt = 0;
}
/**
* This is the runnable class for the <code>WakeupManager</code>,
* since we need different implementations of
* <code>WakeupManager.run</code> and
* <code>TaskManager.run</code>.
*/
private class RetryTime implements Runnable {
/**
* Time to retry the task.
*/
public void run() {
synchronized (RetryTask.this) {
ticket = null;
}
manager.add(RetryTask.this);
}
};
}