/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.kernel;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.kernel.schedule.ScheduledTask;
import com.sun.sgs.kernel.KernelRunnable;
import com.sun.sgs.kernel.Priority;
import com.sun.sgs.kernel.RecurringTaskHandle;
import com.sun.sgs.kernel.TaskQueue;
/**
* Package-private implementation of {@code ScheduledTask} that is used to
* maintain state about tasks accepted by {@code TransactionScheduler} and
* passed on its backing {@code SchedulerQueue}.
* <p>
* This class implements some of the {@code Future} interface so that the
* associated task can be cancelled or waited on to finish. The resulting
* value provided by a call to {@code get} is {@code null} if the task
* completed successfully, or the {@code Throwable} that caused the task
* to fail permanently in the case of failure. See the documentation on
* the associated methods for more details on how interruption is handled.
* <p>
* Note: This class is located in the {@code com.sun.sgs.impl.kernel} package
* instead of the {@code com.sun.sgs.impl.kernel.schedule} package because
* it needs to be a package-private implementation for use by the schedulers.
*/
final class ScheduledTaskImpl implements ScheduledTask {
// the common, immutable aspects of a task
private final KernelRunnable task;
private final Identity owner;
private final long period;
// the common, mutable aspects of a task
private volatile Priority priority;
private volatile long startTime;
private RecurringTaskHandle recurringTaskHandle = null;
private int tryCount = 0;
private TaskQueue queue = null;
private volatile long timeout;
private volatile Throwable lastFailure = null;
// state associated with the lifetime of the task
private enum State {
/* The task can be started running. */
RUNNABLE,
/* The task is currently running. */
RUNNING,
/* The task was interrupted while running but can be tried again. */
INTERRUPTED,
/* The task has completed. */
COMPLETED,
/* The task was cancelled. */
CANCELLED
}
private State state = State.RUNNABLE;
// the result of the task, if the state is COMPLETED; null if the task
// completed successfully, or the cause of a permanent failure
private Throwable result = null;
/**
* We use the builder pattern here to avoid constructor explosion.
* This builder takes three required parameters, {@code task},
* {@code owner}, and {@code priority} and then provides setter methods
* for each of the remaining optional parameters for building a
* {@code ScheduledTaskImpl} object.
* <p>
* Example usage:
* <p>
* <pre>
* ScheduledTaskImpl task = new ScheduledTaskImpl.Builder(
* task, owner, priority).period(1000).build();
* </pre>
*/
static class Builder {
// attributes used to build the ScheduledTaskImpl
private KernelRunnable task;
private Identity owner;
private Priority priority;
private long startTime = System.currentTimeMillis();
private long period = NON_RECURRING;
private long timeout = defaultTimeout;
private RecurringTaskHandle recurringTaskHandle = null;
// default values
private static long defaultTimeout = -2;
/**
* Constructs a {@code Builder} object that takes three required
* parameters for building a {@code ScheduledTaskImpl}.
*
* @param task the <code>KernelRunnable</code> to run
* @param owner the <code>Identity</code> of the task owner
* @param priority the <code>Priority</code> of the task
*/
Builder(KernelRunnable task, Identity owner, Priority priority) {
this.task = task;
this.owner = owner;
this.priority = priority;
}
/**
* Constructs a {@code Builder} object to build a
* {@code ScheduledTaskImpl} object with all of the same values as the
* given task except for the {@code startTime}, {@code timeout}, and
* {@code maxConcurrency} which are set to their default values.
*
* @param task existing {@code ScheduledTaskImpl}
*/
Builder(ScheduledTaskImpl task) {
this(task.task, task.owner, task.priority);
this.period = task.period;
this.recurringTaskHandle = task.recurringTaskHandle;
}
/**
* Setter for setting the start time of a new {@code ScheduledTaskImpl}.
*
* @param startTime the time at which to start in milliseconds since
* January 1, 1970
* @return this {@code Builder} object
*/
Builder startTime(long startTime) {
this.startTime = startTime;
return this;
}
/**
* Setter for setting the period of a new {@code ScheduledTaskImpl}
*
* @param period the delay between recurring executions, or
* {@code NON_RECURRING}
* @return this {@code Builder} object
*/
Builder period(long period) {
this.period = period;
return this;
}
/**
* Setter for setting the timeout of a new {@code ScheduledTaskImpl}
*
* @param timeout the transaction timeout to use for this task or
* {@code UNBOUNDED}
* @return this {@code Builder} object
*/
Builder timeout(long timeout) {
this.timeout = timeout;
return this;
}
/**
* Set the default value of {@code timeout} for new instances
* of {@code ScheduledTaskImpl} built with a builder.
*
* @param timeout default value of timeout
*/
static void setDefaultTimeout(long timeout) {
defaultTimeout = timeout;
}
/**
* Builds a {@code ScheduledTaskImpl} object from the attributes
* in this builder.
*
* @return a {@code ScheduledTaskImpl} object
*/
ScheduledTaskImpl build() {
return new ScheduledTaskImpl(this);
}
}
/**
* Creates an instance of {@code ScheduledTaskImpl} from the given
* {@code Builder}.
*
* @param builder the {@code Builder} object that contains each of the
* desired attributes of the new task
*/
private ScheduledTaskImpl(Builder builder) {
if (builder.task == null) {
throw new NullPointerException("Task cannot be null");
}
if (builder.owner == null) {
throw new NullPointerException("Owner cannot be null");
}
if (builder.priority == null) {
throw new NullPointerException("Priority cannot be null");
}
if (builder.timeout < 0 && builder.timeout != ScheduledTask.UNBOUNDED) {
throw new IllegalStateException("Timeout cannot be negative");
}
this.task = builder.task;
this.owner = builder.owner;
this.priority = builder.priority;
this.startTime = builder.startTime;
this.period = builder.period;
this.timeout = builder.timeout;
this.recurringTaskHandle = builder.recurringTaskHandle;
}
/** Implementation of ScheduledTask interface. */
/** {@inheritDoc} */
public KernelRunnable getTask() {
return task;
}
/** {@inheritDoc} */
public Identity getOwner() {
return owner;
}
/** {@inheritDoc} */
public Priority getPriority() {
return priority;
}
/** {@inheritDoc} */
public long getStartTime() {
return startTime;
}
/** {@inheritDoc} */
public long getPeriod() {
return period;
}
/** {@inheritDoc} */
public int getTryCount() {
return tryCount;
}
/** {@inheritDoc} */
public long getTimeout() {
return timeout;
}
/** {@inheritDoc} */
public Throwable getLastFailure() {
return lastFailure;
}
/** {@inheritDoc} */
public void setPriority(Priority priority) {
this.priority = priority;
}
/** {@inheritDoc} */
public boolean isRecurring() {
return period != NON_RECURRING;
}
/** {@inheritDoc} */
public RecurringTaskHandle getRecurringTaskHandle() {
return recurringTaskHandle;
}
/** {@inheritDoc} */
public synchronized boolean isCancelled() {
return state == State.CANCELLED;
}
/**
* {@inheritDoc}
* <p>
* Note that in the current scheduler implementation this is only
* called at the point where {@code runTask()} is using its calling
* thread to run a task. In this case calling cancel() will always
* return, so no extra logic has been implemented to make sure that
* canceling the task keeps it from re-trying. Were this method to be
* used when a task could be handed-off between threads or
* re-tried many times then this implementation should probably be
* extended to note the cancelation request and disallow further
* attempts at execution.
*/
public synchronized boolean cancel(boolean allowInterrupt)
throws InterruptedException
{
if (isDone()) {
return false;
}
while (state == State.RUNNING) {
try {
wait();
} catch (InterruptedException ie) {
if (allowInterrupt) {
throw ie;
}
}
}
if (!isDone()) {
state = State.CANCELLED;
notifyAll();
return true;
}
return false;
}
/** Package-private utility methods. */
/**
* Sets the transaction timeout for this task.
*
* @param timeout the new transaction timeout for this task
*/
void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* Returns {@code null} if the task completed successfully, or the
* {@code Throwable} that caused the task to fail permanently. If the
* task has not yet completed then this will block until the result
* is known or the caller is interrupted. An {@code InterruptedException}
* is thrown either if the calling thread is interrupted before a result
* is known or if the task is cancelled meaning that no result is known.
*/
synchronized Throwable get() throws InterruptedException {
// wait for the task to finish
while (!isDone()) {
wait();
}
if (state == State.CANCELLED) {
throw new InterruptedException("interrupted while getting result");
}
return result;
}
/** Re-sets the starting time to the now. */
void resetStartTime() {
startTime = System.currentTimeMillis();
}
/** Returns whether the task has finished. */
synchronized boolean isDone() {
return ((state == State.COMPLETED) || (state == State.CANCELLED));
}
/**
* Sets the state of this task to {@code running}, returning {@code false}
* if the task has already been cancelled or has completed, or if the
* state is not being changed.
*/
synchronized boolean setRunning(boolean running) {
if (isDone()) {
return false;
}
if (running) {
if ((state != State.RUNNABLE) && (state != State.INTERRUPTED)) {
return false;
}
state = State.RUNNING;
} else {
if (state != State.RUNNING) {
return false;
}
state = State.RUNNABLE;
notifyAll();
}
return true;
}
/**
* Similar to calling {@code setRunning(false)} except that no waiters
* are notified of the state change. This is used when the task's thread
* is interrupted and we don't know if the task is going to be re-runnable
* in a new thread or has to be dropped.
*/
synchronized boolean setInterrupted() {
if (state == State.RUNNING) {
state = State.INTERRUPTED;
return true;
}
return false;
}
/** Returns whether this task is currently running. */
synchronized boolean isRunning() {
return state == State.RUNNING;
}
/**
* Sets the state of this task to done, with the result of the task being
* the provided {@code Throwable} which may be {@code null} to indicate
* that the task completed successfully. If the task is not currently
* running or was not previously interrupted then this method has no effect.
* Otherwise, the result is set and the task is marked as completed.
*/
synchronized void setDone(Throwable result) {
if ((state != State.RUNNING) && (state != State.INTERRUPTED)) {
return;
}
state = State.COMPLETED;
this.result = result;
notifyAll();
}
/**
* Sets this task's handle or throws {@code IllegalStateException} if
* the task is not recurring.
*/
void setRecurringTaskHandle(RecurringTaskHandle handle) {
if (!isRecurring()) {
throw new IllegalStateException("Not a recurring task");
}
recurringTaskHandle = handle;
}
/**
* Increments the try count (the number of times that this task has been
* attempted).
*/
void incrementTryCount() {
tryCount++;
}
/**
* Sets the {@code Throwable} that caused the last failure of this task.
*
* @param lastFailure the last failure
*/
void setLastFailure(Throwable lastFailure) {
this.lastFailure = lastFailure;
}
/**
* Sets the {@code TaskQueue} associated with this task. Typically this
* is used to track dependency, so that when this task is completed,
* the next task can be fetched from the queue.
*/
void setTaskQueue(TaskQueue queue) {
this.queue = queue;
}
/**
* Returns the {@code TaskQueue} associated with this task or {@code null}
* if no queue was set. This is typically the source of dependent tasks
* where this task was submitted.
*/
TaskQueue getTaskQueue() {
return queue;
}
/**
* Provides some diagnostic detail about this task.
*
* @return a <code>String</code> representation of the task.
*/
public String toString() {
return task.getBaseTaskType() + "[owner:" + owner.getName() + "]";
}
}