/* * 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.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import javax.annotation.Nullable; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.HasTaskChildren; 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.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.ReferenceWithError; import org.apache.brooklyn.util.repeat.Repeater; import org.apache.brooklyn.util.time.CountdownTimer; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.collect.Iterables; public class Tasks { private static final Logger log = LoggerFactory.getLogger(Tasks.class); /** convenience for setting "blocking details" on any task where the current thread is running; * typically invoked prior to a wait, for transparency to a user; * then invoked with 'null' just after the wait */ public static String setBlockingDetails(String description) { Task<?> current = current(); if (current instanceof TaskInternal) return ((TaskInternal<?>)current).setBlockingDetails(description); return null; } public static void resetBlockingDetails() { Task<?> current = current(); if (current instanceof TaskInternal) ((TaskInternal<?>)current).resetBlockingDetails(); } public static Task<?> setBlockingTask(Task<?> blocker) { Task<?> current = current(); if (current instanceof TaskInternal) return ((TaskInternal<?>)current).setBlockingTask(blocker); return null; } public static void resetBlockingTask() { Task<?> current = current(); if (current instanceof TaskInternal) ((TaskInternal<?>)current).resetBlockingTask(); } /** convenience for setting "blocking details" on any task where the current thread is running, * while the passed code is executed; often used from groovy as * <pre>{@code withBlockingDetails("sleeping 5s") { Thread.sleep(5000); } }</pre> * If code block is null, the description is set until further notice (not cleareed). */ @SuppressWarnings("rawtypes") public static <T> T withBlockingDetails(String description, Callable<T> code) throws Exception { Task current = current(); if (code==null) { log.warn("legacy invocation of withBlockingDetails with null code block, ignoring"); return null; } String prevBlockingDetails = null; if (current instanceof TaskInternal) { prevBlockingDetails = ((TaskInternal)current).setBlockingDetails(description); } try { return code.call(); } finally { if (current instanceof TaskInternal) ((TaskInternal)current).setBlockingDetails(prevBlockingDetails); } } /** the {@link Task} where the current thread is executing, if executing in a Task, otherwise null; * if the current task is a proxy, this returns the target of that proxy */ @SuppressWarnings("rawtypes") public static Task current() { return getFinalProxyTarget(BasicExecutionManager.getPerThreadCurrentTask().get()); } public static Task<?> getFinalProxyTarget(Task<?> task) { if (task==null) return null; Task<?> proxy = ((TaskInternal<?>)task).getProxyTarget(); if (proxy==null || proxy.equals(task)) return task; return getFinalProxyTarget(proxy); } /** creates a {@link ValueResolver} instance which allows significantly more customization than * the various {@link #resolveValue(Object, Class, ExecutionContext)} methods here */ public static <T> ValueResolver<T> resolving(Object v, Class<T> type) { return new ValueResolver<T>(v, type); } public static ValueResolver.ResolverBuilderPretype resolving(Object v) { return new ValueResolver.ResolverBuilderPretype(v); } /** @see #resolveValue(Object, Class, ExecutionContext, String) */ public static <T> T resolveValue(Object v, Class<T> type, @Nullable ExecutionContext exec) throws ExecutionException, InterruptedException { return new ValueResolver<T>(v, type).context(exec).get(); } /** attempt to resolve the given value as the given type, waiting on futures, submitting if necessary, * and coercing as allowed by TypeCoercions; * contextMessage (optional) will be displayed in status reports while it waits (e.g. the name of the config key being looked up). * if no execution context supplied (null) this method will throw an exception if the object is an unsubmitted task */ public static <T> T resolveValue(Object v, Class<T> type, @Nullable ExecutionContext exec, String contextMessage) throws ExecutionException, InterruptedException { return new ValueResolver<T>(v, type).context(exec).description(contextMessage).get(); } /** * @see #resolveDeepValue(Object, Class, ExecutionContext, String) */ public static Object resolveDeepValue(Object v, Class<?> type, ExecutionContext exec) throws ExecutionException, InterruptedException { return resolveDeepValue(v, type, exec, null); } /** * Resolves the given object, blocking on futures and coercing it to the given type. If the object is a * map or iterable (or a list of map of maps, etc, etc) then walks these maps/iterables to convert all of * their values to the given type. For example, the following will return a list containing a map with "1"="true": * * {@code Object result = resolveDeepValue(ImmutableList.of(ImmutableMap.of(1, true)), String.class, exec)} * * To perform a deep conversion of futures contained within Iterables or Maps without coercion of each element, * the type should normally be Object, not the type of the collection. This differs from * {@link #resolveValue(Object, Class, ExecutionContext, String)} which will accept Map and Iterable * as the required type. */ public static <T> T resolveDeepValue(Object v, Class<T> type, ExecutionContext exec, String contextMessage) throws ExecutionException, InterruptedException { return new ValueResolver<T>(v, type).context(exec).deep(true).description(contextMessage).get(); } /** sets extra status details on the current task, if possible (otherwise does nothing). * the extra status is presented in Task.getStatusDetails(true) */ public static void setExtraStatusDetails(String notes) { Task<?> current = current(); if (current instanceof TaskInternal) ((TaskInternal<?>)current).setExtraStatusText(notes); } public static <T> TaskBuilder<T> builder() { return TaskBuilder.<T>builder(); } private static Task<?>[] asTasks(TaskAdaptable<?> ...tasks) { Task<?>[] result = new Task<?>[tasks.length]; for (int i=0; i<tasks.length; i++) result[i] = tasks[i].asTask(); return result; } public static Task<List<?>> parallel(TaskAdaptable<?> ...tasks) { return parallelInternal("parallelised tasks", asTasks(tasks)); } public static Task<List<?>> parallel(String name, TaskAdaptable<?> ...tasks) { return parallelInternal(name, asTasks(tasks)); } public static Task<List<?>> parallel(Iterable<? extends TaskAdaptable<?>> tasks) { return parallel(asTasks(Iterables.toArray(tasks, TaskAdaptable.class))); } public static Task<List<?>> parallel(String name, Iterable<? extends TaskAdaptable<?>> tasks) { return parallelInternal(name, asTasks(Iterables.toArray(tasks, TaskAdaptable.class))); } private static Task<List<?>> parallelInternal(String name, Task<?>[] tasks) { return Tasks.<List<?>>builder().displayName(name).parallel(true).add(tasks).build(); } public static Task<List<?>> sequential(TaskAdaptable<?> ...tasks) { return sequentialInternal("sequential tasks", asTasks(tasks)); } public static Task<List<?>> sequential(String name, TaskAdaptable<?> ...tasks) { return sequentialInternal(name, asTasks(tasks)); } public static TaskFactory<?> sequential(TaskFactory<?> ...taskFactories) { return sequentialInternal("sequential tasks", taskFactories); } public static TaskFactory<?> sequential(String name, TaskFactory<?> ...taskFactories) { return sequentialInternal(name, taskFactories); } public static Task<List<?>> sequential(List<? extends TaskAdaptable<?>> tasks) { return sequential(asTasks(Iterables.toArray(tasks, TaskAdaptable.class))); } public static Task<List<?>> sequential(String name, List<? extends TaskAdaptable<?>> tasks) { return sequential(name, asTasks(Iterables.toArray(tasks, TaskAdaptable.class))); } private static Task<List<?>> sequentialInternal(String name, Task<?>[] tasks) { return Tasks.<List<?>>builder().displayName(name).parallel(false).add(tasks).build(); } private static TaskFactory<?> sequentialInternal(final String name, final TaskFactory<?> ...taskFactories) { return new TaskFactory<TaskAdaptable<?>>() { @Override public TaskAdaptable<?> newTask() { TaskBuilder<List<?>> tb = Tasks.<List<?>>builder().displayName(name).parallel(false); for (TaskFactory<?> tf: taskFactories) tb.add(tf.newTask().asTask()); return tb.build(); } }; } /** returns the first tag found on the given task which matches the given type, looking up the submission hierarachy if necessary */ @SuppressWarnings("unchecked") public static <T> T tag(@Nullable Task<?> task, Class<T> type, boolean recurseHierarchy) { // support null task to make it easier for callers to walk hierarchies if (task==null) return null; for (Object tag: task.getTags()) if (type.isInstance(tag)) return (T)tag; if (!recurseHierarchy) return null; return tag(task.getSubmittedByTask(), type, true); } public static boolean isAncestorCancelled(Task<?> t) { if (t==null) return false; if (t.isCancelled()) return true; return isAncestorCancelled(t.getSubmittedByTask()); } public static boolean isQueued(TaskAdaptable<?> task) { return ((TaskInternal<?>)task.asTask()).isQueued(); } public static boolean isSubmitted(TaskAdaptable<?> task) { return ((TaskInternal<?>)task.asTask()).isSubmitted(); } public static boolean isQueuedOrSubmitted(TaskAdaptable<?> task) { return ((TaskInternal<?>)task.asTask()).isQueuedOrSubmitted(); } /** * Adds the given task to the given context. Does not throw an exception if the addition fails. * @return true if the task was added, false otherwise. */ public static boolean tryQueueing(TaskQueueingContext adder, TaskAdaptable<?> task) { if (task==null || isQueued(task)) return false; try { adder.queue(task.asTask()); return true; } catch (Exception e) { if (log.isDebugEnabled()) log.debug("Could not add task "+task+" at "+adder+": "+e); return false; } } /** see also {@link #resolving(Object)} which gives much more control about submission, timeout, etc */ public static <T> Supplier<T> supplier(final TaskAdaptable<T> task) { return new Supplier<T>() { @Override public T get() { return task.asTask().getUnchecked(); } }; } /** return all children tasks of the given tasks, if it has children, else empty list */ public static Iterable<Task<?>> children(Task<?> task) { if (task instanceof HasTaskChildren) return ((HasTaskChildren)task).getChildren(); return Collections.emptyList(); } /** returns failed tasks */ public static Iterable<Task<?>> failed(Iterable<Task<?>> subtasks) { return Iterables.filter(subtasks, new Predicate<Task<?>>() { @Override public boolean apply(Task<?> input) { return input.isError(); } }); } /** returns the task, its children, and all its children, and so on; * @param root task whose descendants should be iterated * @param parentFirst whether to put parents before children or after */ public static Iterable<Task<?>> descendants(Task<?> root, final boolean parentFirst) { Iterable<Task<?>> descs = Iterables.concat(Iterables.transform(Tasks.children(root), new Function<Task<?>,Iterable<Task<?>>>() { @Override public Iterable<Task<?>> apply(Task<?> input) { return descendants(input, parentFirst); } })); if (parentFirst) return Iterables.concat(Collections.singleton(root), descs); else return Iterables.concat(descs, Collections.singleton(root)); } /** returns the error thrown by the task if {@link Task#isError()}, or null if no error or not done */ public static Throwable getError(Task<?> t) { if (t==null) return null; if (!t.isDone()) return null; if (t.isCancelled()) return new CancellationException(); try { t.get(); return null; } catch (Throwable error) { // do not propagate as we are pretty much guaranteed above that it wasn't this // thread which originally threw the error return error; } } public static Task<Void> fail(final String name, final Throwable optionalError) { return Tasks.<Void>builder().dynamic(false).displayName(name).body(new Runnable() { public void run() { if (optionalError!=null) throw Exceptions.propagate(optionalError); else throw new RuntimeException("Failed: "+name); } }).build(); } public static Task<Void> warning(final String message, final Throwable optionalError) { log.warn(message); return TaskTags.markInessential(fail(message, optionalError)); } /** marks the current task inessential; this mainly matters if the task is running in a parent * {@link TaskQueueingContext} and we don't want the parent to fail if this task fails * <p> * no-op (silently ignored) if not in a task */ public static void markInessential() { Task<?> task = Tasks.current(); if (task==null) { TaskQueueingContext qc = DynamicTasks.getTaskQueuingContext(); if (qc!=null) task = qc.asTask(); } if (task!=null) { TaskTags.markInessential(task); } } /** causes failures in subtasks of the current task not to fail the parent; * no-op if not in a {@link TaskQueueingContext}. * <p> * essentially like a {@link #markInessential()} on all tasks in the current * {@link TaskQueueingContext}, including tasks queued subsequently */ @Beta public static void swallowChildrenFailures() { Preconditions.checkNotNull(DynamicTasks.getTaskQueuingContext(), "Task queueing context required here"); TaskQueueingContext qc = DynamicTasks.getTaskQueuingContext(); if (qc!=null) { qc.swallowChildrenFailures(); } } /** as {@link TaskTags#addTagDynamically(TaskAdaptable, Object)} but for current task, skipping if no current task */ public static void addTagDynamically(Object tag) { Task<?> t = Tasks.current(); if (t!=null) TaskTags.addTagDynamically(t, tag); } /** * Workaround for limitation described at {@link Task#cancel(boolean)}; * internal method used to allow callers to wait for underlying tasks to finished in the case of cancellation. * <p> * It is irritating that {@link FutureTask} sync's object clears the runner thread, * so even if {@link BasicTask#getInternalFuture()} is used, there is no means of determining if the underlying object is done. * The {@link Task#getEndTimeUtc()} seems the only way. * * @return true if tasks ended; false if timed out **/ @Beta public static boolean blockUntilInternalTasksEnded(Task<?> t, Duration timeout) { CountdownTimer timer = timeout.countdownTimer(); if (t==null) return true; if (t instanceof ScheduledTask) { boolean result = ((ScheduledTask)t).blockUntilNextRunFinished(timer.getDurationRemaining()); if (!result) return false; } t.blockUntilEnded(timer.getDurationRemaining()); while (true) { if (t.getEndTimeUtc()>=0) return true; // above should be sufficient; but just in case, trying the below Thread tt = t.getThread(); if (t instanceof ScheduledTask) { ((ScheduledTask)t).blockUntilNextRunFinished(timer.getDurationRemaining()); return true; } else { if (tt==null || !tt.isAlive()) { if (!t.isCancelled()) { // may happen for a cancelled task, interrupted after submit but before start log.warn("Internal task thread is dead or null ("+tt+") but task not ended: "+t.getEndTimeUtc()+" ("+t+")"); } return true; } } if (timer.isExpired()) return false; Time.sleep(Repeater.DEFAULT_REAL_QUICK_PERIOD); } } /** returns true if either the current thread or the current task is interrupted/cancelled */ public static boolean isInterrupted() { if (Thread.currentThread().isInterrupted()) return true; Task<?> t = current(); if (t==null) return false; return t.isCancelled(); } private static class WaitForRepeaterCallable implements Callable<Boolean> { protected Repeater repeater; protected boolean requireTrue; public WaitForRepeaterCallable(Repeater repeater, boolean requireTrue) { this.repeater = repeater; this.requireTrue = requireTrue; } @Override public Boolean call() { ReferenceWithError<Boolean> result; Tasks.setBlockingDetails(repeater.getDescription()); try { result = repeater.runKeepingError(); } finally { Tasks.resetBlockingDetails(); } if (Boolean.TRUE.equals(result.getWithoutError())) return true; if (result.hasError()) throw Exceptions.propagate(result.getError()); if (requireTrue) throw new IllegalStateException("timeout - "+repeater.getDescription()); return false; } } /** @return a {@link TaskBuilder} which tests whether the repeater terminates with success in its configured timeframe, * returning true or false depending on whether repeater succeed */ public static TaskBuilder<Boolean> testing(Repeater repeater) { return Tasks.<Boolean>builder().body(new WaitForRepeaterCallable(repeater, false)) .displayName("waiting for condition") .description("Testing whether " + getTimeoutString(repeater) + ": "+repeater.getDescription()); } /** @return a {@link TaskBuilder} which requires that the repeater terminate with success in its configured timeframe, * throwing if it does not */ public static TaskBuilder<?> requiring(Repeater repeater) { return Tasks.<Boolean>builder().body(new WaitForRepeaterCallable(repeater, true)) .displayName("waiting for condition") .description("Requiring " + getTimeoutString(repeater) + ": " + repeater.getDescription()); } private static String getTimeoutString(Repeater repeater) { Duration timeout = repeater.getTimeLimit(); if (timeout==null || Duration.PRACTICALLY_FOREVER.equals(timeout)) return "eventually"; return "in "+timeout; } }