/*
* 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 org.apache.brooklyn.util.core.task;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ExecutionContext;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.api.mgmt.TaskAdaptable;
import org.apache.brooklyn.api.mgmt.TaskFactory;
import org.apache.brooklyn.api.mgmt.TaskQueueingContext;
import org.apache.brooklyn.api.mgmt.TaskWrapper;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
/**
* 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> taskQueueingContext = new ThreadLocal<TaskQueueingContext>();
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<T> implements TaskWrapper<T> {
private final Task<T> task;
private final boolean wasQueued;
private ExecutionContext execContext = null;
private TaskQueueingResult(TaskAdaptable<T> task, boolean wasQueued) {
this.task = task.asTask();
this.wasQueued = wasQueued;
}
@Override
public Task<T> asTask() {
return task;
}
@Override
public Task<T> 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);
}
/** specifies an execContext to use if the task has to be explicitly submitted;
* if omitted it will attempt to find one based on the current thread's context */
public TaskQueueingResult<T> executionContext(ExecutionContext execContext) {
this.execContext = execContext;
return this;
}
/** as {@link #executionContext(ExecutionContext)} but inferring from the entity */
public TaskQueueingResult<T> executionContext(Entity entity) {
this.execContext = ((EntityInternal)entity).getManagementSupport().getExecutionContext();
return this;
}
private boolean orSubmitInternal() {
if (!wasQueued()) {
if (isQueuedOrSubmitted()) {
log.warn("Redundant call to execute "+getTask()+"; skipping");
return false;
} else {
ExecutionContext ec = execContext;
if (ec==null)
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,
* requiring an entity execution context (will try to find a default if not set) */
public TaskQueueingResult<T> orSubmitAsync() {
orSubmitInternal();
return this;
}
/** convenience for setting {@link #executionContext(ExecutionContext)} then submitting async */
public TaskQueueingResult<T> orSubmitAsync(Entity entity) {
executionContext(entity);
return orSubmitAsync();
}
/** 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);
* requiring an entity execution context (will try to find a default if not set) */
public TaskQueueingResult<T> orSubmitAndBlock() {
if (orSubmitInternal()) task.getUnchecked();
return this;
}
/** convenience for setting {@link #executionContext(ExecutionContext)} then submitting blocking */
public TaskQueueingResult<T> orSubmitAndBlock(Entity entity) {
executionContext(entity);
return orSubmitAndBlock();
}
/** blocks for the task to be completed
* <p>
* 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.
* <p>
* throws if there are any errors
*/
public T andWaitForSuccess() {
return task.getUnchecked();
}
public void orCancel() {
if (!wasQueued()) {
task.cancel(false);
}
}
}
/**
* Tries to add the task to the current addition context if there is one, otherwise does nothing.
* <p/>
* Call {@link TaskQueueingResult#orSubmitAsync() orSubmitAsync()} on the returned
* {@link TaskQueueingResult TaskQueueingResult} to handle execution of tasks in a
* {@link BasicExecutionContext}.
*/
public static <T> TaskQueueingResult<T> queueIfPossible(TaskAdaptable<T> task) {
TaskQueueingContext adder = getTaskQueuingContext();
boolean result = false;
if (adder!=null)
result = Tasks.tryQueueing(adder, task);
return new TaskQueueingResult<T>(task, result);
}
/** @see #queueIfPossible(TaskAdaptable) */
public static <T> TaskQueueingResult<T> queueIfPossible(TaskFactory<? extends TaskAdaptable<T>> 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
* <p>
* throws if it cannot add */
public static <T> Task<T> queueInTaskHierarchy(Task<T> 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);
}
/**
* Queues the given task.
* <p/>
* This method is only valid within a dynamic task. Use {@link #queueIfPossible(TaskAdaptable)}
* and {@link TaskQueueingResult#orSubmitAsync()} if the calling context is a basic task.
*
* @param task The task to queue
* @throws IllegalStateException if no task queueing context is available
* @return The queued task
*/
public static <V extends TaskAdaptable<?>> V queue(V task) {
try {
Preconditions.checkNotNull(task, "Task to queue cannot be null");
Preconditions.checkState(!Tasks.isQueued(task), "Task to queue must not yet be queued: %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);
}
}
/** @see #queue(org.apache.brooklyn.api.mgmt.TaskAdaptable) */
public static void queue(TaskAdaptable<?> task1, TaskAdaptable<?> task2, TaskAdaptable<?> ...tasks) {
queue(task1);
queue(task2);
for (TaskAdaptable<?> task: tasks) queue(task);
}
/** @see #queue(org.apache.brooklyn.api.mgmt.TaskAdaptable) */
public static <T extends TaskAdaptable<?>> T queue(TaskFactory<T> taskFactory) {
return queue(taskFactory.newTask());
}
/** @see #queue(org.apache.brooklyn.api.mgmt.TaskAdaptable) */
public static void queue(TaskFactory<?> task1, TaskFactory<?> task2, TaskFactory<?> ...tasks) {
queue(task1.newTask());
queue(task2.newTask());
for (TaskFactory<?> task: tasks) queue(task.newTask());
}
/** @see #queue(org.apache.brooklyn.api.mgmt.TaskAdaptable) */
public static <T> Task<T> queue(String name, Callable<T> job) {
return DynamicTasks.queue(Tasks.<T>builder().displayName(name).body(job).build());
}
/** @see #queue(org.apache.brooklyn.api.mgmt.TaskAdaptable) */
public static <T> Task<T> queue(String name, Runnable job) {
return DynamicTasks.queue(Tasks.<T>builder().displayName(name).body(job).build());
}
/** queues the task if needed, i.e. if it is not yet submitted (so it will run),
* or if it is submitted but not queued and we are in a queueing context (so it is available for informational purposes) */
public static <T extends TaskAdaptable<?>> T queueIfNeeded(T task) {
if (!Tasks.isQueued(task)) {
if (Tasks.isSubmitted(task) && getTaskQueuingContext()==null) {
// already submitted and not in a queueing context, don't try to queue
} else {
// needs submitting, put it in the queue
// (will throw an error if we are not a queueing context)
queue(task);
}
}
return task;
}
/** submits/queues the given task if needed, and gets the result (unchecked)
* only permitted in a queueing context (ie a DST main job) if the task is not yet submitted */
// things get really confusing if you try to queueInTaskHierarchy -- easy to cause deadlocks!
public static <T> T get(TaskAdaptable<T> t) {
return queueIfNeeded(t).asTask().getUnchecked();
}
/** As {@link #drain(Duration, boolean)} waiting forever and throwing the first error
* (excluding errors in inessential tasks),
* then returning the last task in the queue (which is guaranteed to have finished without error,
* if this method returns without throwing) */
public static Task<?> waitForLast() {
drain(null, true);
// this call to last is safe, as the above guarantees everything will have run
// (on errors the above will throw so we won't come here)
List<Task<?>> q = DynamicTasks.getTaskQueuingContext().getQueue();
return q.isEmpty() ? null : Iterables.getLast(q);
}
/** Calls {@link TaskQueueingContext#drain(Duration, boolean, boolean)} on the current task context */
public static TaskQueueingContext drain(Duration optionalTimeout, boolean throwFirstError) {
TaskQueueingContext qc = DynamicTasks.getTaskQueuingContext();
Preconditions.checkNotNull(qc, "Cannot drain when there is no queueing context");
qc.drain(optionalTimeout, false, throwFirstError);
return qc;
}
/** as {@link Tasks#swallowChildrenFailures()} but requiring a {@link TaskQueueingContext}. */
@Beta
public static void swallowChildrenFailures() {
Preconditions.checkNotNull(DynamicTasks.getTaskQueuingContext(), "Task queueing context required here");
Tasks.swallowChildrenFailures();
}
/** same as {@link Tasks#markInessential()}
* (but included here for convenience as it is often used in conjunction with {@link DynamicTasks}) */
public static void markInessential() {
Tasks.markInessential();
}
/** queues the task if possible, otherwise submits it asynchronously; returns the task for callers to
* {@link Task#getUnchecked()} or {@link Task#blockUntilEnded()} */
public static <T> Task<T> submit(TaskAdaptable<T> task, Entity entity) {
return queueIfPossible(task).orSubmitAsync(entity).asTask();
}
/** Breaks the parent-child relation between Tasks.current() and the task passed,
* making the new task a top-level one at the target entity.
* To make it visible in the UI, also tag the task with:
* .tag(BrooklynTaskTags.tagForContextEntity(entity))
* .tag(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG)
*/
public static <T> Task<T> submitTopLevelTask(TaskAdaptable<T> task, Entity entity) {
Task<?> currentTask = BasicExecutionManager.getPerThreadCurrentTask().get();
BasicExecutionManager.getPerThreadCurrentTask().set(null);
try {
return Entities.submit(entity, task).asTask();
} finally {
BasicExecutionManager.getPerThreadCurrentTask().set(currentTask);
}
}
}