From efb5ad9f5fe48a77b6cd14e7bd2b25e3b13ecb1f Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 3 Jun 2014 16:44:56 +0200 Subject: Initial commit --- .../org/openslx/taskmanager/api/AbstractTask.java | 284 +++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java (limited to 'api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java') diff --git a/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java b/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java new file mode 100644 index 0000000..cc837ba --- /dev/null +++ b/api/src/main/java/org/openslx/taskmanager/api/AbstractTask.java @@ -0,0 +1,284 @@ +package org.openslx.taskmanager.api; + +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.openslx.taskmanager.api.TaskStatus.StatusCode; + +import com.google.gson.annotations.Expose; + +public abstract class AbstractTask implements Runnable +{ + + private static final long RELEASE_DELAY = 5l * 60l * 1000l; + private static final Logger LOG = Logger.getLogger( AbstractTask.class ); + + /* + * To be set from task invocation (json data) + */ + + /** + * The id of the task instance. + */ + @Expose + private String id = null; + /** + * Parent task. This task won't be started as long as the parent task currently + * waiting for execution or is being executed. Otherwise this task is available for execution. + * Note that MAX_INSTANCES is still being taken into account. Set to null to ignore. + */ + @Expose + private String parentTask = null; + /** + * If the parent task failed to execute, don't run this task and fail immediately. + */ + @Expose + private boolean failOnParentFail = true; + + /* + * Variables we're working with - these should never be set from incoming (json) data + */ + + /** + * Maximum age of a task, if it's not freed explicitly. + */ + private static final long MAX_TASK_AGE = 24l * 3600l * 1000l; + /** + * timeMillis when this task will be removed. This will be set automatically to something + * reasonable like 24 hours, once the job has finished. This is to prevent clogging the task + * manager with finished jobs over time. Note that you can explicitly remove a job using the + * "release" command. + */ + private volatile long removalDeadline = System.currentTimeMillis() + MAX_TASK_AGE; + /** + * True if init() has been called. Task will not start if this is false. + */ + private volatile boolean initDone = false; + /** + * Status of Task + */ + private TaskStatus status = TaskStatus.ts_waiting; + /** + * Reference to parent task + */ + private AbstractTask parent = null; + + /** + * Default constructor which should not be overridden + */ + public AbstractTask() + { + this.id = UUID.randomUUID().toString(); + } + + /* + * Overridable methods + */ + + /** + * Initialize the task; method used by the {@link Taskmanager}. + * Put your own initialization code in initTask() + */ + public final boolean init( AbstractTask parent ) + { + if ( this.initDone ) { + LOG.fatal( "init() called twice on " + this.getClass().getSimpleName() ); + System.exit( 1 ); + } + this.parent = parent; + this.status = new TaskStatus( StatusCode.TASK_WAITING, this.id ); + this.initDone = true; + boolean ret; + try { + ret = this.initTask(); + } catch ( Throwable t ) { + ret = false; + } + if ( !ret ) { + this.status.statusCode = StatusCode.TASK_ERROR; + } + return ret; + } + + /** + * Your own initialization code. This is run synchronously within the network + * handling thread, so do NOT put anything here that might take longer than a few milliseconds! + * You should usually only validate the input to the task here, and return false if it is + * invalid. + * + * @return - true if the task should be executed by the scheduler (ie init was successful) + * - false if init was not successful, or you don't need to do any more processing + */ + protected abstract boolean initTask(); + + /** + * This is where you put your huge work stuff that takes ages. + */ + protected abstract boolean execute(); + + /* + * Final methods + */ + + /** + * Get id of parent task. + * + * @return id of parent task, null if no parent set + * + */ + public String getParentTaskId() + { + return this.parentTask; + } + + /** + * Set the custom status data object to be returned on status requests. + * For simple tasks it should be sufficient to set this once before executing + * the task starts, and then just update fields in that class while the + * task is running. For cases where you have complicated data structures or + * multiple values that need to stay in sync you could create a new instance + * every time, fill it with values, and then call this method. + * + * @param obj the object containing you specific task data + */ + protected final void setStatusObject( Object obj ) + { + status.setStatusObject( obj ); + } + + /** + * Get current status of task. + */ + public final TaskStatus getStatus() + { + if ( this.initDone && this.parentTask != null ) { + final StatusCode parentStatus = parent.getStatusCode(); + switch ( parentStatus ) { + case DUPLICATE_ID: + case NO_SUCH_INSTANCE: + case NO_SUCH_TASK: + case NO_SUCH_CONSTRUCTOR: + case PARENT_FAILED: + case TASK_ERROR: + if ( this.failOnParentFail ) + this.status.statusCode = StatusCode.PARENT_FAILED; + this.parentTask = null; + break; + default: + break; + } + } + return this.status; + } + + /** + * Get status code if task. + * + * @return + */ + public final StatusCode getStatusCode() + { + return getStatus().getStatusCode(); + } + + /** + * Get id of task + */ + public final String getId() + { + return this.id; + } + + /** + * Getter for failOnParentTask + */ + public final boolean getFailOnParentFail() + { + return this.failOnParentFail; + } + + /** + * Release the task: If the task is still pending/running, it will be removed from the task + * manager as soon as it finishes. So you can't query for its result later. If the task has + * already finished (either successful or failed), it will be removed (almost) immediately. + */ + public final void release() + { + this.removalDeadline = Math.min( this.removalDeadline, System.currentTimeMillis() + RELEASE_DELAY ); + } + + /** + * Can this task be started? This checks all the conditions: + * - Has this task been initialized yet? + * - Is it actually waiting for execution? + * - Does it depend on a parent task? + * -- If so, did the parent finish? + * -- Or did it fail? + * --- If so, should this task only run if it didn't fail? + * + * @return true iff this task can be started + */ + public final boolean canStart() + { + if ( !this.initDone || this.id == null || this.getStatus().getStatusCode() != StatusCode.TASK_WAITING ) { + return false; + } + if ( this.parent == null ) + return true; + return parent.getStatusCode() != StatusCode.TASK_WAITING && parent.getStatusCode() != StatusCode.TASK_PROCESSING; + } + + /** + * Checks whether this task can be removed from the task manager. + * A task can be removed if it is not executing or waiting for execution, and + * its removal deadline has been reached. + * + * @return true if it can be released + */ + public final boolean canBeReleased() + { + if ( this.status.statusCode == StatusCode.TASK_WAITING || this.status.statusCode == StatusCode.TASK_PROCESSING ) + return false; + return this.removalDeadline != 0 && System.currentTimeMillis() > this.removalDeadline; + } + + /** + * Execute the task, wrapped in some sanity checks. + */ + @Override + public final void run() + { + synchronized ( this ) { + if ( this.status.statusCode != StatusCode.TASK_WAITING ) + throw new RuntimeException( "Tried to launch task " + this.getClass().getSimpleName() + " twice!" ); + if ( !this.initDone ) + throw new RuntimeException( "Tried to launch " + this.getClass().getSimpleName() + " without initializing it!" ); + this.status.statusCode = StatusCode.TASK_PROCESSING; + } + boolean ret; + try { + ret = execute(); + } catch ( Throwable t ) { + LOG.warn( "Task " + this.getClass().getSimpleName() + " failed with uncaught exception: " + t.toString() ); + ret = false; + } + if ( ret ) { + this.status.statusCode = StatusCode.TASK_FINISHED; + } else { + this.status.statusCode = StatusCode.TASK_ERROR; + } + } + + /** + * This is called when a client requests the status of this task. In case you + * want to return complex structures like Lists, which are not thread safe, you + * might want to keep that list outside the status class you return, and only + * create a copy of it for the status class in this function. + * If you only return more or less atomic data, you don't need to override + * this function + */ + protected void updateStatus() + { + + } +} -- cgit v1.2.3-55-g7522