All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.kernel.async.ExecutionBuilder Maven / Gradle / Ivy

/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.async;

import sirius.kernel.commons.Watch;
import sirius.kernel.health.Average;
import sirius.kernel.health.Exceptions;

import javax.annotation.CheckReturnValue;
import javax.annotation.ParametersAreNonnullByDefault;
import java.time.Duration;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Builder pattern for forking or starting sub tasks.
 * 

* Used by {@link Tasks#executor(String)} to construct an execution for a given subtask. Can be used to specify whether * the current {@link CallContext} is forked, or if a new one is started. Also one can specify whether and how a given * task might be dropped on system overload conditions. *

* Most of the time this builder will be used to either call {@link #fork(Runnable)} or {@link #start(Runnable)} * to either fork the current CallContext or to start a sub task with a new one. Also a drop handler can be * supplied using {@link #dropOnOverload(Runnable)} to gracefully handle system overload conditions. */ @ParametersAreNonnullByDefault public class ExecutionBuilder { private static final String SYSTEM_ASYNC = "ASYNC"; private Tasks tasks; /** * Internal class which takes care of passing along the CallContext and for storing the configuration made by the * ExecutionBuilder. */ static class TaskWrapper implements Runnable { /** * Determines the executor (thread pool to use) */ String category; /** * Contains the work to do */ Runnable runnable; /** * Keep the current {@link CallContext} or create a new one? */ boolean fork; /** * What to do if the we drop the task because the system is overloaded */ Runnable dropHandler; /** * The {@code CallContext} to use when {@code fork} is true. */ CallContext ctx; /** * Used to communicate the completion / abort of the task */ Future promise = new Future(); /** * Contains the inertnally computed task number */ long jobNumber; /** * Filled by {@link Tasks#execute(TaskWrapper)} with the appropriate {@code Average} of the * underlying executor to measure throughput. */ Average durationAverage; /** * If a synchorinzer is present this can be used to limit the call frequency of a task */ long intervalMinLength; /** * Once the timestamp of the last invocation is known, this will contain the earliest possible * timestamp to execute this task */ long waitUntil; /** * The synchronizer to use when {@code frequency} is set */ Object synchronizer; /** * Prepares the execution of this task while checking all preconditions. */ void prepare() { if (fork) { ctx = CallContext.getCurrent().fork(); } if (runnable == null) { throw new IllegalArgumentException("Please provide a runnable for me to execute!"); } } @Override public void run() { try { Watch w = Watch.start(); try { if (ctx == null) { CallContext.initialize(); } else { CallContext.setCurrent(ctx); } TaskContext.get().setSystem(SYSTEM_ASYNC).setSubSystem(category).setJob(String.valueOf(jobNumber)); runnable.run(); promise.success(null); } finally { CallContext.detach(); durationAverage.addValue(w.elapsedMillis()); } } catch (Exception t) { Exceptions.handle(Tasks.LOG, t); promise.fail(t); } } @Override public String toString() { return category; } protected void drop() { if (dropHandler != null) { dropHandler.run(); } promise.doNotLogErrors(); promise.fail(new RejectedExecutionException()); } } private TaskWrapper wrapper = new TaskWrapper(); /** * Generates a new ExecutionBuilder for the given category. * * @param category the category which is used to determine which executor to use */ ExecutionBuilder(Tasks tasks, String category) { this.tasks = tasks; wrapper.category = category; } /** * Specifies to fork the current CallContext while executing the given task. * * @param task the task to execute. * @return a {@code Future} representing the execution created by this builder. */ public Future fork(Runnable task) { wrapper.runnable = task; wrapper.fork = true; tasks.execute(wrapper); return wrapper.promise; } /** * Specifies to create a new CallContext while executing the given task. * * @param task the task to execute. * @return a {@code Future} representing the execution created by this builder. */ public Future start(Runnable task) { wrapper.runnable = task; wrapper.fork = false; tasks.execute(wrapper); return wrapper.promise; } /** * Specifies that the given task can be dropped (ignored) in system overload conditions, if at least the given * handler is called. * * @param dropHandler the handler which is informed if the task is dropped due to system overload conditions. * @return this for fluent builder calls. */ @CheckReturnValue public ExecutionBuilder dropOnOverload(Runnable dropHandler) { wrapper.dropHandler = dropHandler; return this; } /** * Determines the minimal interval which has to elapse between two consecutive tasks scheduled for the given * {@code synchronizer}. *

* If the execution is requested 'too early' the scheduler will put the task into a queue and defer its execution. * If a task for the same synchronizer is deferred already, this task will be dropped completely. * * @param synchronizer the object to synchronize on * @param minimalIntervalDuration the minimal duration of the interval * @return this for fluent builder calls. */ public ExecutionBuilder minInterval(Object synchronizer, Duration minimalIntervalDuration) { this.wrapper.intervalMinLength = TimeUnit.SECONDS.toMillis(minimalIntervalDuration.getSeconds()) + TimeUnit.NANOSECONDS.toMillis( minimalIntervalDuration.getNano()); this.wrapper.synchronizer = synchronizer; return this; } /** * Determines the maximal call frequency for tasks scheduled for the given {@code synchronizer}. *

* If the execution is requested 'too early' the scheduler will put the task into a queue and defer its execution. * If a task for the same synchronizer is deferred already, this task will be dropped completely. * * @param synchronizer the object to synchronize on * @param ticksPerSecond the call frequency in Hertz. * @return this for fluent builder calls. */ public ExecutionBuilder frequency(Object synchronizer, double ticksPerSecond) { return minInterval(synchronizer, Duration.ofNanos(Math.round(1_000_000_000d / ticksPerSecond))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy