brooklyn.util.task.DynamicTasks Maven / Gradle / Ivy
Show all versions of brooklyn-core Show documentation
package brooklyn.util.task;
import java.util.List;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.TaskFactory;
import brooklyn.management.TaskQueueingContext;
import brooklyn.management.TaskWrapper;
import brooklyn.util.exceptions.Exceptions;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
/**
* Contains static methods which detect and use the current {@link TaskQueueingContext} to execute tasks.
*
* @since 0.6.0
*/
@Beta
public class DynamicTasks {
private static final Logger log = LoggerFactory.getLogger(DynamicTasks.class);
private static final ThreadLocal taskQueueingContext = new ThreadLocal();
public static void setTaskQueueingContext(TaskQueueingContext newTaskQC) {
taskQueueingContext.set(newTaskQC);
}
public static TaskQueueingContext getThreadTaskQueuingContext() {
return taskQueueingContext.get();
}
public static TaskQueueingContext getTaskQueuingContext() {
TaskQueueingContext adder = getThreadTaskQueuingContext();
if (adder!=null) return adder;
Task> t = Tasks.current();
if (t instanceof TaskQueueingContext) return (TaskQueueingContext) t;
return null;
}
public static void removeTaskQueueingContext() {
taskQueueingContext.remove();
}
public static class TaskQueueingResult implements TaskWrapper {
private final Task task;
private final boolean wasQueued;
private TaskQueueingResult(TaskAdaptable task, boolean wasQueued) {
this.task = task.asTask();
this.wasQueued = wasQueued;
}
@Override
public Task asTask() {
return task;
}
@Override
public Task getTask() {
return task;
}
/** returns true if the task was queued */
public boolean wasQueued() {
return wasQueued;
}
/** returns true if the task either is currently queued or has been submitted */
public boolean isQueuedOrSubmitted() {
return wasQueued || Tasks.isQueuedOrSubmitted(task);
}
private boolean orSubmitInternal() {
if (!wasQueued()) {
if (isQueuedOrSubmitted()) {
log.warn("Redundant call to execute "+getTask()+"; skipping");
return false;
} else {
BasicExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext();
if (ec==null)
throw new IllegalStateException("Cannot execute "+getTask()+" without an execution context; ensure caller is in an ExecutionContext");
ec.submit(getTask());
return true;
}
} else {
return false;
}
}
/** causes the task to be submitted (asynchronously) if it hasn't already been */
public TaskQueueingResult orSubmitAsync() {
orSubmitInternal();
return this;
}
/** causes the task to be submitted *synchronously* if it hasn't already been submitted;
* useful in contexts such as libraries where callers may be either on a legacy call path
* (which assumes all commands complete immediately)
* */
public TaskQueueingResult orSubmitAndBlock() {
if (orSubmitInternal()) task.getUnchecked();
return this;
}
/** blocks for the task to be completed
*
* needed in any context where subsequent commands assume the task has completed.
* not needed in a context where the task is simply being built up and queued.
*
* throws if there are any errors
*/
public void andWaitForSuccess() {
task.getUnchecked();
}
}
/** tries to add the task to the current addition context if there is one, otherwise does nothing */
public static TaskQueueingResult queueIfPossible(TaskAdaptable task) {
TaskQueueingContext adder = getTaskQueuingContext();
boolean result = false;
if (adder!=null)
result = Tasks.tryQueueing(adder, task);
return new TaskQueueingResult(task, result);
}
public static TaskQueueingResult queueIfPossible(TaskFactory extends TaskAdaptable> task) {
return queueIfPossible(task.newTask());
}
/** adds the given task to the nearest task addition context,
* either set as a thread-local, or in the current task, or the submitter of the task, etc
*
* throws if it cannot add */
public static Task queueInTaskHierarchy(Task task) {
Preconditions.checkNotNull(task, "Task to queue cannot be null");
Preconditions.checkState(!Tasks.isQueuedOrSubmitted(task), "Task to queue must not yet be submitted: {}", task);
TaskQueueingContext adder = getTaskQueuingContext();
if (adder!=null) {
if (Tasks.tryQueueing(adder, task)) {
log.debug("Queued task {} at context {} (no hierarchy)", task, adder);
return task;
}
}
Task> t = Tasks.current();
Preconditions.checkState(t!=null || adder!=null, "No task addition context available for queueing task "+task);
while (t!=null) {
if (t instanceof TaskQueueingContext) {
if (Tasks.tryQueueing((TaskQueueingContext)t, task)) {
log.debug("Queued task {} at hierarchical context {}", task, t);
return task;
}
}
t = t.getSubmittedByTask();
}
throw new IllegalStateException("No task addition context available in current task hierarchy for adding task "+task);
}
public static > V queue(V task) {
try {
Preconditions.checkNotNull(task, "Task to queue cannot be null");
Preconditions.checkState(!Tasks.isQueuedOrSubmitted(task), "Task to queue must not yet be submitted: %s", task);
TaskQueueingContext adder = getTaskQueuingContext();
if (adder==null)
throw new IllegalStateException("Task "+task+" cannot be queued here; no queueing context available");
adder.queue(task.asTask());
return task;
} catch (Throwable e) {
log.warn("Error queueing "+task+" (rethrowing): "+e);
throw Exceptions.propagate(e);
}
}
public static void queue(TaskAdaptable> task1, TaskAdaptable> task2, TaskAdaptable> ...tasks) {
queue(task1);
queue(task2);
for (TaskAdaptable> task: tasks) queue(task);
}
public static > T queue(TaskFactory taskFactory) {
return queue(taskFactory.newTask());
}
public static void queue(TaskFactory> task1, TaskFactory> task2, TaskFactory> ...tasks) {
queue(task1.newTask());
queue(task2.newTask());
for (TaskFactory> task: tasks) queue(task.newTask());
}
public static Task queue(String name, Callable job) {
return DynamicTasks.queue(Tasks.builder().name(name).body(job).build());
}
public static Task queue(String name, Runnable job) {
return DynamicTasks.queue(Tasks.builder().name(name).body(job).build());
}
public static > T queueIfNeeded(T task) {
if (!Tasks.isQueuedOrSubmitted(task))
queue(task);
return task;
}
/** submits the given task if needed, and gets the result (unchecked)
* only permitted in a queueing context (ie a DST main job) */
// things get really confusing if you try to queueInTaskHierarchy -- easy to cause deadlocks!
public static T get(TaskAdaptable t) {
return queueIfNeeded(t).asTask().getUnchecked();
}
/** Waits for the last task queued in this context to complete;
* it throws if there is a problem, but happily returns null if there is no last task.
*
* Preferred over {@link #last()}.get() because this waits on all tasks,
* in sequentially (so that blocking information is always accurate) */
public static Task> waitForLast() {
TaskQueueingContext qc = DynamicTasks.getTaskQueuingContext();
Preconditions.checkNotNull(qc, "Cannot wait when their is no queueing context");
List> q = qc.getQueue();
Task> last = null;
do {
for (Task> t: q) {
last = t;
last.getUnchecked();
}
} while (last!=qc.last());
return last;
}
}