fn) {
181 | initializers.add(fn);
182 | }
183 | }
184 |
185 | public static Config getConfig() {
186 | return globalConfig;
187 | }
188 |
189 | public static void setConfig(Config config) {
190 | globalConfig = config;
191 | }
192 |
193 | /**
194 | * Set a TaskRunner creation function that will apply to every Orchestrator created in this this thread.
195 | * Each newly-created TaskRunner will be added first in the taskRunner chain.
196 | *
197 | * The mechanism uses ThreadLocal internally and those must be closed properly to ensure cleanup of threadLocal
198 | * state. A typical usage is thus:
199 | *
{@code
200 | * try (LaneRunner laneRunner = GlobalOrchestratorConfig.interceptFirstOnCreate(MyTaskRunner::new)) {
201 | * // Perform any logic here that might create Orchestrators at any point
202 | * laneRunner.runners.forEach(r->performRunnerOperation(r));
203 | * }
204 | * }
205 | *
206 | * Multiple TaskRunners can be installed in this way by nesting these calls.
207 | *
208 | * @param createFn to create a TaskRunner instance of the desired type
209 | * @param class of any TaskRunners that will be created
210 | * @return a LaneRunner whose 'futures' member variable provides access to any TaskRunners actually created
211 | */
212 | public static LaneRunner interceptFirstOnCreate(Supplier createFn) {
213 | return new LaneRunner(createFn,true);
214 | }
215 |
216 | /**
217 | * Set a TaskRunner creation function that will apply to every Orchestrator created in this this thread.
218 | * Each newly-created TaskRunner will be added first in the taskRunner chain.
219 | *
220 | * See other considerations as described in {@link #interceptFirstOnCreate(Supplier)}.
221 | *
222 | * @param createFn to create a TaskRunner instance of the desired type
223 | * @param class of any TaskRunners that will be created
224 | * @return a LaneRunner whose 'futures' member variable provides access to any TaskRunners actually created
225 | */
226 | public static LaneRunner interceptLastOnCreate(Supplier createFn) {
227 | return new LaneRunner(createFn,false);
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/LaneRunner.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.util.ArrayList;
20 | import java.util.Collections;
21 | import java.util.List;
22 | import java.util.function.Supplier;
23 |
24 | /**
25 | * Establishes a TaskRunner creation capability that for each new {@link Orchestrator} created in this thread
26 | * will be applied and recorded for later retrieval.
27 | *
28 | * @author bremccarthy
29 | * @param type of TaskRunner to create
30 | * @see GlobalOrchestratorConfig#interceptFirstOnCreate(Supplier)
31 | */
32 | public class LaneRunner implements AutoCloseable {
33 |
34 | /**
35 | * Exposes all TaskRunners created by the thread that invoked the constructor, up until the time this LaneRunner
36 | * was closed (if it has been closed). This will usually be just zero or one, but implementations are free to
37 | * create as many orchestrators as desired. Beware that orchestrators created in spawned threads will not be
38 | * visible to this instance.
39 | *
40 | * Care should be taken if this list is retrieved asynchronously since the list might still be added to.
41 | * A safe strategy is to retrieve the list at a point when all processing for this thread is completed. Otherwise,
42 | * the standard Java practices around avoiding ConcurrentModificationExceptions when traversing lists should be practiced.
43 | */
44 | public final List runners = Collections.synchronizedList(new ArrayList<>());
45 |
46 | private static final ThreadLocal> threadLocal = new ThreadLocal<>();
47 | private final Supplier createFn;
48 | private final boolean firstElseLast;
49 | private LaneRunner> previous = null; // Non-null when nested
50 |
51 | LaneRunner(Supplier createFn, boolean firstElseLast) {
52 | this.createFn = createFn;
53 | previous = threadLocal.get();
54 | threadLocal.set(this);
55 | this.firstElseLast = firstElseLast;
56 | }
57 |
58 | static void apply(Orchestrator orchestrator) {
59 | LaneRunner> laneRunner = threadLocal.get();
60 | intercept(orchestrator,laneRunner);
61 | }
62 |
63 | private static void intercept(Orchestrator orchestrator, LaneRunner> laneRunner) {
64 | if (laneRunner != null) {
65 | TaskRunner taskRunner = laneRunner.add();
66 | if (laneRunner.firstElseLast) {
67 | orchestrator.firstInterceptWith(taskRunner);
68 | } else {
69 | orchestrator.lastInterceptWith(taskRunner);
70 | }
71 | intercept(orchestrator,laneRunner.previous);
72 | }
73 | }
74 |
75 | private T add() {
76 | T taskRunner = createFn.get();
77 | runners.add(taskRunner);
78 | return taskRunner;
79 | }
80 |
81 | @Override
82 | public void close() throws Exception {
83 | if (previous==null) {
84 | threadLocal.remove();
85 | } else {
86 | threadLocal.set(previous);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/PlaceHolderRunner.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | /**
20 | * Maintains TaskRunners in a linked list, rather than requiring TaskRunners themselves
21 | * to maintain that list.
22 | *
23 | * @author Brendan McCarthy
24 | */
25 | class PlaceHolderRunner implements TaskRun {
26 | private final Object fromBefore;
27 | private final Thread parentThread;
28 | private final TaskRun taskRun;
29 | private final TaskRunner next;
30 |
31 | PlaceHolderRunner(TaskRunner nextTaskRunner, Thread parentThread, TaskRun taskRun) {
32 | this.next = nextTaskRunner;
33 | this.parentThread = parentThread;
34 | this.taskRun = taskRun;
35 | this.fromBefore = next.before(taskRun);
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return "PRunner(" + taskRun + ")";
41 | }
42 |
43 | @Override
44 | public String getName() {
45 | return taskRun.getName();
46 | }
47 |
48 | @Override
49 | public String getTaskPlusMethodName() {
50 | return taskRun.getTaskPlusMethodName();
51 | }
52 |
53 | @Override
54 | public void formatActualSignature(StringBuilder sb) {
55 | taskRun.formatActualSignature(sb);
56 | }
57 |
58 | @Override
59 | public boolean isLight() {
60 | return taskRun.isLight();
61 | }
62 |
63 | @Override
64 | public TaskInterface> getTask() {
65 | return taskRun.getTask();
66 | }
67 |
68 | @Override
69 | public Object run() {
70 | Object rv = next.executeTaskMethod(taskRun, parentThread, fromBefore);
71 | Binding.completeRunner(next, taskRun, fromBefore, rv);
72 | return rv;
73 | }
74 |
75 | @Override
76 | public long getStartedAt() {
77 | return taskRun.getStartedAt();
78 | }
79 |
80 | @Override
81 | public long getEndedAt() {
82 | return taskRun.getEndedAt();
83 | }
84 |
85 | @Override
86 | public long getCompletedAt() {
87 | return taskRun.getCompletedAt();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/ReflectionBinding.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import com.ebay.bascomtask.annotations.Light;
20 |
21 | import java.lang.reflect.InvocationTargetException;
22 | import java.lang.reflect.Method;
23 | import java.util.concurrent.CompletableFuture;
24 |
25 | /**
26 | * Binding for a task with a method to be called through reflection. This is the standard case for user POJO tasks.
27 | *
28 | * @author Brendan McCarthy
29 | */
30 | class ReflectionBinding extends Binding {
31 | private final TaskWrapper taskWrapper;
32 | private final Object userTask;
33 | private final Method method;
34 | private final Object[] args;
35 | private final boolean light;
36 | private final boolean runSpawned;
37 |
38 | ReflectionBinding(Engine engine, TaskWrapper taskWrapper, Object userTask, Method method, Object[] args) {
39 | super(engine);
40 | this.userTask = userTask;
41 | this.taskWrapper = taskWrapper;
42 | this.method = method;
43 | this.args = args;
44 |
45 | // Only one of these should be set -- that is also true in TaskWrapper
46 | // An explicit call on the task overrules a @Light annotation if present
47 | this.runSpawned = taskWrapper.isRunSpawned();
48 | this.light = taskWrapper.isLight()
49 | || (Utils.getAnnotation(userTask, method, Light.class) != null && !taskWrapper.explicitRunSpawn());
50 |
51 | if (args != null) {
52 | for (Object next : args) {
53 | if (next instanceof CompletableFuture) {
54 | CompletableFuture> cf = (CompletableFuture>) next;
55 | ensureWrapped(cf, true);
56 | }
57 | }
58 | }
59 | }
60 |
61 | @Override
62 | public boolean isLight() {
63 | return light;
64 | }
65 |
66 | @Override
67 | public boolean isRunSpawned() {
68 | return runSpawned;
69 | }
70 |
71 | @Override
72 | String doGetExecutionName() {
73 | String taskName = taskWrapper.getName();
74 | return taskName + "." + method.getName();
75 | }
76 |
77 | @Override
78 | public TaskInterface> getTask() {
79 | return (TaskInterface>) userTask;
80 | }
81 |
82 | @Override
83 | protected Object invokeTaskMethod() {
84 | try {
85 | // Don't require public access, especially because of poor JVM exception messages, e.g. failure to make
86 | // an interface public, when BT accessed as a library, can otherwise result in
87 | // IllegalAccessException ... cannot access a member of interface ... with modifiers "public abstract"
88 | method.setAccessible(true);
89 | return method.invoke(userTask, args);
90 | } catch (InvocationTargetException itx) {
91 | Throwable actual = itx.getCause();
92 | RuntimeException re;
93 | if (actual instanceof RuntimeException) {
94 | re = (RuntimeException) actual;
95 | } else {
96 | re = new RuntimeException(actual);
97 | }
98 | throw re;
99 | } catch (IllegalAccessException e) {
100 | throw new RuntimeException("Unable to invoke method", e);
101 | }
102 | }
103 |
104 | @Override
105 | public void formatActualSignature(StringBuilder sb) {
106 | Utils.formatFullSignature(sb, getTaskPlusMethodName(), args);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/SpawnMode.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | /**
20 | * Defines thread spawning behavior for the BascomTask framework.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public enum SpawnMode {
25 | /**
26 | * Spawn whenever more than one task can be started at the same time. This is the default behavior.
27 | */
28 | WHEN_NEEDED(true),
29 |
30 | /**
31 | * Like {@link #WHEN_NEEDED}, but avoids any attempt to reuse main thread for processing.
32 | * That 'reuse' occurs when the main thread is sitting idle with no work to do while
33 | * spawned threads need themselves to spawn threads.
34 | */
35 | WHEN_NEEDED_NO_REUSE(false),
36 |
37 | /**
38 | * Avoid using main (calling thread) to execute task methods, spawning threads instead,
39 | * unless the task methods are marked as 'light'. This keeps the calling thread free
40 | * for other purposes.
41 | */
42 | NEVER_MAIN(false),
43 |
44 | /**
45 | * Always spawn except for 'light' task methods. This is a stronger assertion then {@link #NEVER_MAIN},
46 | * as the main thread will similarly not execute any tasks. Every task will run in its own logical thread
47 | * (as in a pull from the thread pool, where physical threads may of course be reused).
48 | */
49 | ALWAYS_SPAWN(false),
50 |
51 | /**
52 | * Never spawn under any circumstance. Every task will be run in the calling thread when activated.
53 | */
54 | NEVER_SPAWN(false),
55 |
56 | /**
57 | * Don't spawn unless a {@link TaskInterface#runSpawned()} request is made on a task.
58 | */
59 | DONT_SPAWN_UNLESS_EXPLICIT(true);
60 |
61 | private final boolean mainThreadReusable;
62 |
63 | SpawnMode(boolean mainThreadReusable) {
64 | this.mainThreadReusable = mainThreadReusable;
65 | }
66 |
67 | /**
68 | * Returns true if this mode allows for the main thread can be picked up while idle and otherwise
69 | * waiting for a CompletableFuture to complete, and used to to process spawning task methods.
70 | *
71 | * @return true iff main thread is reusable
72 | */
73 | public boolean isMainThreadReusable() {
74 | return mainThreadReusable;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/SupplierTask.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import com.ebay.bascomtask.annotations.Light;
20 |
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.function.BiFunction;
23 | import java.util.function.Function;
24 | import java.util.function.Supplier;
25 |
26 | /**
27 | * Function tasks added through 'fn()' methods on {@link Orchestrator} allow CompletableFuture tasks to
28 | * be created from lambda expressions that supply a return value. The inputs are bundled into the task,
29 | * and the result is obtained by invoking {@link #apply()} on this task.
30 | *
31 | * @author Brendan McCarthy
32 | */
33 | public interface SupplierTask extends TaskInterface> {
34 | /**
35 | * Creates a CompletableFuture around the lambda expression.
36 | *
37 | * @return evaluated CompletableFuture
38 | */
39 | CompletableFuture apply();
40 |
41 | /**
42 | * An SupplierTask that takes no arguments.
43 | *
44 | * @param type of return result
45 | */
46 | class SupplierTask0 extends BaseFnTask> implements SupplierTask {
47 | private final Supplier fn;
48 |
49 | public SupplierTask0(Engine engine, Supplier fn) {
50 | super(engine);
51 | this.fn = fn;
52 | }
53 |
54 | @Override
55 | @Light
56 | public CompletableFuture apply() {
57 | return complete(fn.get());
58 | }
59 | }
60 |
61 | /**
62 | * An SupplierTask that takes 1 argument.
63 | *
64 | * @param type of input
65 | * @param type of return result
66 | */
67 | class SupplierTask1 extends BaseFnTask> implements SupplierTask {
68 | private final Function fn;
69 | final BascomTaskFuture input;
70 |
71 | public SupplierTask1(Engine engine, CompletableFuture input, Function fn) {
72 | super(engine);
73 | this.fn = fn;
74 | this.input = ensureWrapped(input,true);
75 | }
76 |
77 | @Override
78 | Binding> doActivate(Binding> pending, TimeBox timeBox) {
79 | return input.activate(this, pending, timeBox);
80 | }
81 |
82 | @Override
83 | @Light
84 | public CompletableFuture apply() {
85 | T value = get(input);
86 | return complete(fn.apply(value));
87 | }
88 | }
89 |
90 | /**
91 | * An SupplierTask that takes 2 arguments.
92 | *
93 | * @param type of input
94 | * @param type of return result
95 | */
96 | class SupplierTask2 extends BaseFnTask> implements SupplierTask {
97 | private final BiFunction fn;
98 | final BascomTaskFuture firstInput;
99 | final BascomTaskFuture secondInput;
100 |
101 | public SupplierTask2(Engine engine, CompletableFuture firstInput, CompletableFuture secondInput, BiFunction fn) {
102 | super(engine);
103 | this.fn = fn;
104 | this.firstInput = ensureWrapped(firstInput,true);
105 | this.secondInput = ensureWrapped(secondInput,true);
106 | }
107 |
108 | @Override
109 | Binding> doActivate(Binding> pending, TimeBox timeBox) {
110 | pending = firstInput.activate(this, pending, timeBox);
111 | return secondInput.activate(this,pending,timeBox);
112 | }
113 |
114 | @Override
115 | @Light
116 | public CompletableFuture apply() {
117 | T firstValue = get(firstInput);
118 | U secondValue = get(secondInput);
119 | return complete(fn.apply(firstValue,secondValue));
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TaskInterface.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import com.ebay.bascomtask.exceptions.MisplacedTaskMethodException;
20 |
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.concurrent.CompletionException;
23 | import java.util.concurrent.ExecutionException;
24 | import java.util.concurrent.Future;
25 |
26 | /**
27 | * Common interface that all user POJO task interfaces must implement in order to be added to an Orchestrator,
28 | * for example like this:
29 | * {@code
30 | * interface IDelay implements TaskInterface {
31 | * // Add test methods here
32 | * }
33 | *
34 | * class Delay implements IDelay {...}
35 | * }
36 | *
37 | * Task classes such as Delay
or its interface IDelay
above can implement/extend other
38 | * interfaces, but among all of them only one should be designated as argument to TaskInterface in the manner above.
39 | *
40 | *
There are no required methods to implement from this interface since all have default implementations. For
41 | * uncommon cases some of these methods may usefully be overridden as noted in each javadoc. Several convenience
42 | * methods are also provided for wrapping and unwrapping values in/from CompletableFutures. There use is completely
43 | * optional.
44 | *
45 | * @param identifies that interface that defines task methods
46 | * @author Brendan McCarthy
47 | * @see Orchestrator#task(TaskInterface)
48 | */
49 | public interface TaskInterface {
50 |
51 | /**
52 | * Provides a name for this task, useful for logging and profiling.
53 | *
54 | * TaskWrappers (as returned by {@link Orchestrator#task(TaskInterface)}, already support this method which
55 | * therefore works in expressions like {@code $.task(myTask).name("myTask").myMethod()}.
56 | * That is the typical usage, so there is normally no need to override this method, but subclasses can
57 | * override if desired to also enable invocation directly on the task, e.g.
58 | * {@code $.task(myTask.name("myTask")).myMethod()}
59 | *
60 | * @param name to set
61 | * @return this
62 | */
63 | default T name(String name) {
64 | throw new MisplacedTaskMethodException(this,"name");
65 | }
66 |
67 | /**
68 | * Returns the name of this task, useful for logging and profiling. Subclasses can optionally
69 | * override if they want to expose something other than the class name as the default name. Whether or not
70 | * overridden, any TaskWrapper (the object that is returned by adding a task to an Orchestrator) that wraps
71 | * this object (there can be more than one) will manage its own name and use this value returned here only
72 | * if not explicitly set during task wiring.
73 | *
74 | * @return name
75 | */
76 | default String getName() {
77 | return getClass().getSimpleName();
78 | }
79 |
80 | /**
81 | * Forces inline execution of task methods on this object, i.e. prevents a new thread from being
82 | * spawned for them when an Orchestrator
might otherwise do so.
83 | *
84 | * TaskWrappers (as returned by {@link Orchestrator#task(TaskInterface)}, already support this method which
85 | * therefore works in expressions like {@code $.task(myTask).light().myMethod()}
86 | * That is the typical usage, so there is normally no need to override this method, but subclasses can
87 | * override if desired to also enable invocation directly on the task, e.g.
88 | * {@code $.task(myTask.light()).myMethod()}.
89 | *
90 | * This call reverses any previous call to {@link #runSpawned()}. The impact of this call is redundant for
91 | * task methods that already have {@link com.ebay.bascomtask.annotations.Light} annotation.
92 | *
93 | * @return this
94 | */
95 | default T light() {
96 | throw new MisplacedTaskMethodException(this,"light");
97 | }
98 |
99 | /**
100 | * Indicates the default weight of task methods that do not have a {@link com.ebay.bascomtask.annotations.Light}
101 | * annotation.
102 | *
103 | * @return true iff by default a thread should never be spawned for task methods on this class
104 | */
105 | default boolean isLight() {
106 | return false;
107 | }
108 |
109 | /**
110 | * Forces a new thread to be allocated for task methods on this class, which can be useful to keep the calling
111 | * thread free for other purposes. This overrides the default behavior, which is spawn threads
112 | * when an single thread finds more than one task ready to fire.
113 | *
114 | * TaskWrappers (as returned by {@link Orchestrator#task(TaskInterface)}, already support this method which
115 | * therefore works in expressions like {@code $.task(myTask).runSpawned().myMethod()}.
116 | * That is the typical usage, so there is normally no need to override this method, but subclasses can
117 | * override if desired to also enable invocation directly on the task, e.g.
118 | * {@code $.task(myTask.runSpawned()).myMethod()}
119 | *
120 | *
This call reverses any previous call to {@link #light()}. Its effect is superseded by a
121 | * {@link com.ebay.bascomtask.annotations.Light} annotation on a task method if present.
122 | *
123 | * @return this
124 | */
125 | @SuppressWarnings("unchecked")
126 | default T runSpawned() {
127 | throw new MisplacedTaskMethodException(this,"runSpawned");
128 | }
129 |
130 | /**
131 | * Indicates that task methods should have threads spawned for them.
132 | *
133 | * @return true iff by default a thread should be spawned for task methods on this class
134 | */
135 | default boolean isRunSpawned() {
136 | return false;
137 | }
138 |
139 | /**
140 | * Forces immediate activation of task methods instead of the default behavior which is that task methods
141 | * are only lazily activated according to the rules described in {@link Orchestrator#task(TaskInterface)}.
142 | *
143 | * TaskWrappers (as returned by {@link Orchestrator#task(TaskInterface)}, already support this method which
144 | * therefore works in expressions like {@code $.task(myTask).activate(true).myMethod()}.
145 | * That is the typical usage, so there is normally no need to override this method, but subclasses can
146 | * override if desired to also enable invocation directly on the task, e.g.
147 | * {@code $.task(myTask.activate(true)).myMethod()}
148 | *
149 | * @return this
150 | */
151 | default T activate() {
152 | throw new MisplacedTaskMethodException(this,"activate");
153 | }
154 |
155 | /**
156 | * Indicates that task methods should be activated immediately. Subclasses can override to change the
157 | * default false return value if desired.
158 | *
159 | * @return true iff by default a thread should never be spawned for task methods on this class
160 | */
161 | default boolean isActivate() {
162 | return false;
163 | }
164 |
165 | /**
166 | * Convenience method for subclasses for accessing a CompletableFuture argument. This attempts to unwrap
167 | * {@link java.util.concurrent.CompletionException}s in a more useful than is provided by calling
168 | * the standard {@link CompletableFuture#join} operation.
169 | *
170 | * @param future to retrieve value from
171 | * @param type of value returned
172 | * @return value wrapped by the supplied future, which may or may not be null
173 | */
174 | default R get(CompletableFuture future) {
175 | try {
176 | return future.join();
177 | } catch (CompletionException e) {
178 | Throwable x = e.getCause();
179 | RuntimeException re;
180 | if (x instanceof RuntimeException) {
181 | re = (RuntimeException)x;
182 | } else {
183 | re = new RuntimeException("get() failed",e);
184 | }
185 | throw re;
186 | }
187 | }
188 |
189 | /**
190 | * Convenience method for subclasses that creates a CompletableFuture from a constant value,
191 | * which may or may not be null.
192 | *
193 | * @param arg value to wrap
194 | * @param type of value returned
195 | * @return CompletableFuture that wraps the provided value
196 | */
197 | default CompletableFuture complete(R arg) {
198 | return CompletableFuture.completedFuture(arg);
199 | }
200 |
201 | /**
202 | * Convenience method for subclasses that return a void result. The framework requires a return result
203 | * even for logically void methods in order to expose common methods (such as being able to start
204 | * task execution) on the return result.
205 | *
206 | * @return a Void result
207 | */
208 | default CompletableFuture complete() {
209 | return CompletableFuture.allOf();
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TaskMeta.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | /**
20 | * Exposes execution statistics to {@link TaskRunner}s.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public interface TaskMeta {
25 |
26 | /**
27 | * Returns the time at which this task method was started.
28 | *
29 | * @return start time in ms or 0 if not started
30 | */
31 | long getStartedAt();
32 |
33 | /**
34 | * Returns the time at which this task method was ended. This value would always be greater
35 | * than or equal to {@link #getStartedAt()}.
36 | *
37 | * @return end time in ms or 0 if not ended
38 | */
39 | long getEndedAt();
40 |
41 | /**
42 | * Returns the time at which this task method was completed, which for CompletableFutures is the time
43 | * at which it had a value assigned. This value is always greater than or equal to {@link #getEndedAt()},
44 | * and is greater than the ending time iff the CompletableFuture had !isDone() on method exit.
45 | *
46 | * @return completion time in ms or 0 if not ended
47 | */
48 | long getCompletedAt();
49 |
50 | default boolean completedBefore(TaskMeta that) {
51 | // Since we only measure at ms granularity, have to assume that <= is <
52 | return this.getEndedAt() <= that.getStartedAt();
53 | }
54 |
55 | default boolean overlapped(TaskMeta that) {
56 | if (this.getStartedAt() > that.getEndedAt()) return false;
57 | if (this.getEndedAt() < that.getStartedAt()) return false;
58 | return true;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TaskRun.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | /**
20 | * User task wrapper exposed to {@link TaskRunner}s.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public interface TaskRun extends TaskMeta {
25 | /**
26 | * Returns the user task pojo if there is one. For some internal tasks, there may not be.
27 | *
28 | * @return possibly null userTask
29 | */
30 | TaskInterface> getTask();
31 |
32 | /**
33 | * Returns the task name, e.g. for a user task TASK.METHOD, where TASK is the the user-specified task
34 | * renaming else the class name if none.
35 | *
36 | * @return never null name
37 | */
38 | String getName();
39 |
40 | String getTaskPlusMethodName();
41 |
42 | void formatActualSignature(StringBuilder sb);
43 |
44 | /**
45 | * Indicates whether the task method is 'light', regardless of how that method was marked as light.
46 | *
47 | * @return true iff this task method is light
48 | * @see com.ebay.bascomtask.annotations.Light
49 | * @see TaskInterface#light()
50 | */
51 | boolean isLight();
52 |
53 | /**
54 | * Invokes the task method. This should be called from {@link TaskRunner#executeTaskMethod(TaskRun, Thread, Object)},
55 | * and the result of this call should be returned from that method.
56 | *
57 | * @return result of call to task method
58 | */
59 | Object run();
60 | }
61 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TaskRunner.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | /**
20 | * Interface for task execution hooks that allow custom code to be inserted before and after the invocation of
21 | * task methods. Implementing classes can be defined for such purposes as logging or various kinds of profiling or
22 | * performance tracking. Subclass instances can be added for all Orchestrators through {@link GlobalOrchestratorConfig}
23 | * or locally to any individual {@link Orchestrator}. Several built-in versions are also available.
24 | *
25 | * @author Brendan McCarthy
26 | * @see com.ebay.bascomtask.runners.LogTaskRunner
27 | * @see com.ebay.bascomtask.runners.StatTaskRunner
28 | * @see com.ebay.bascomtask.runners.ProfilingTaskRunner
29 | */
30 | public interface TaskRunner {
31 |
32 | /**
33 | * Called before {@link #executeTaskMethod(TaskRun, Thread, Object)}, but in the parent thread.
34 | * If there is no immediate spawning then it will be the same thread. The returned object will
35 | * simply be passed to executeTaskMethod and is otherwise not read or modified by the framework.
36 | *
37 | * @param taskRun that will also be passed to executeTaskMethod
38 | * @return a possibly-null object to pass to executeTaskMethod
39 | */
40 | Object before(TaskRun taskRun);
41 |
42 | /**
43 | * Task invocation -- an implementation should invoke {@link com.ebay.bascomtask.core.TaskRun#run()}
44 | * to complete the invocation (unless there is some reason to prevent it, but then the implementation
45 | * must take responsibility to ensure the all CompletableFuture return values are completed). A typical
46 | * subclass implementation would be to simply surround the call, e.g.:
47 | *
48 | * public Object executeTaskMethod(TaskRun taskRun, Thread parentThread, Object fromBefore) {
49 | * LOG.info("STARTED " + taskRun.getTaskPlusMethodName());
50 | * try {
51 | * taskRun.run()
52 | * } finally {
53 | * LOG.info("ENDED " + taskRun.getTaskPlusMethodName());
54 | * }
55 | * }
56 | *
57 | *
58 | * @param taskRun to execute
59 | * @param parentThread of spawned task, which may be same as Thread.currentThread()
60 | * @param fromBefore value returned from {@link #before(TaskRun)}
61 | * @return return value from task invocation
62 | */
63 | Object executeTaskMethod(TaskRun taskRun, Thread parentThread, Object fromBefore);
64 |
65 | /**
66 | * Called once any CompletableFuture return result completes, which may be before the call to
67 | * TaskRun.run() or after if a method returns an incomplete CompletableFuture.
68 | *
69 | * The provided doneOnExit parameter indicates whether a CompletableFuture return value had isDone()==true
70 | * when the method completed. This should not be taken as very close but not exactly precise, since there are
71 | * very small time intervals involved in the steps behind making that determination.
72 | *
73 | * @param taskRun that was executed
74 | * @param fromBefore value returned from {@link #before(TaskRun)}
75 | * @param doneOnExit false iff a CompletableFuture was returned and it was not done when the method exited
76 | */
77 | void onComplete(TaskRun taskRun, Object fromBefore, boolean doneOnExit);
78 | }
79 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TaskWrapper.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.lang.reflect.InvocationHandler;
20 | import java.lang.reflect.InvocationTargetException;
21 | import java.lang.reflect.Method;
22 | import java.util.concurrent.CompletableFuture;
23 |
24 | /**
25 | * Wraps user POJO tasks and is returned by calls to {@link Orchestrator#task(TaskInterface)}. Propagates task method
26 | * calls to the user POJO task and ensures that CompletableFutures returned from those tasks is wrapped with a
27 | * a {@link BascomTaskFuture} and {@link Binding} that together provide various bookkeeping responsibilities for
28 | * task dependency resolution.
29 | *
30 | * @author Brendan McCarthy
31 | */
32 | class TaskWrapper implements InvocationHandler {
33 | private final Engine engine;
34 | private final T original;
35 | private final TaskInterface target; // same instance as original, but needed to call TaskInterface ops
36 | private String name = null;
37 | private boolean light;
38 | private boolean runSpawned;
39 | private boolean activate;
40 |
41 | // Marks explicit wiring calls to runSpawned, necessary in order to keep the priorities consistent among
42 | // the several ways that affect method weight;
43 | private boolean explicitRunSpawn = false;
44 |
45 | public TaskWrapper(Engine engine, T original, TaskInterface target) {
46 | this.engine = engine;
47 | this.original = original;
48 | this.target = target;
49 | this.light = target.isLight();
50 | this.runSpawned = target.isRunSpawned();
51 | this.activate = target.isActivate();
52 | }
53 |
54 | public String getName() {
55 | if (name == null) return target.getName();
56 | return name;
57 | }
58 |
59 | public boolean isLight() {
60 | return light;
61 | }
62 |
63 | public boolean isRunSpawned() {
64 | return runSpawned;
65 | }
66 |
67 | boolean explicitRunSpawn() {
68 | return explicitRunSpawn;
69 | }
70 |
71 | public boolean isActivate() {
72 | return activate;
73 | }
74 |
75 | public Object invoke(Object proxy, Method method, Object[] args)
76 | throws IllegalAccessException, IllegalArgumentException,
77 | InvocationTargetException {
78 | String nm = method.getName();
79 | switch (nm) {
80 | case "hashCode":
81 | return original.hashCode();
82 | case "equals":
83 | return this == args[0];
84 | case "toString":
85 | return "TaskWrapper[" + original + "]";
86 | default:
87 | return fakeTaskMethod(proxy, method, args, nm);
88 | }
89 | }
90 |
91 | private Object fakeTaskMethod(Object proxy, Method method, Object[] args, String targetMethodName)
92 | throws IllegalAccessException, IllegalArgumentException,
93 | InvocationTargetException {
94 |
95 | if ("name".equals(targetMethodName)) {
96 | name = args[0].toString();
97 | return proxy;
98 | } else if ("getName".equals(targetMethodName)) {
99 | return getName();
100 | } else if ("light".equals(targetMethodName)) {
101 | this.runSpawned = false;
102 | this.light = true;
103 | return proxy;
104 | } else if ("runSpawned".equals(targetMethodName)) {
105 | this.light = false;
106 | this.runSpawned = true;
107 | this.explicitRunSpawn = true;
108 | return proxy;
109 | } else if ("activate".equals(targetMethodName)) {
110 | this.activate = true;
111 | return proxy;
112 | }
113 |
114 | if (method.getReturnType().equals(Void.TYPE) || !CompletableFuture.class.isAssignableFrom(method.getReturnType())) {
115 | // Execute immediately if not returning a CompletableFuture
116 | return method.invoke(original, args);
117 | } else {
118 | // Else return a placeholder non-activated CompletableFuture
119 | Binding binding = new ReflectionBinding<>(engine, this, original, method, args);
120 | BascomTaskFuture> bascomTaskFuture = binding.getOutput();
121 | if (activate) {
122 | engine.executeAndReuseUntilReady(bascomTaskFuture);
123 | }
124 | return bascomTaskFuture;
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TernaryConditionalTask.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.util.concurrent.CompletableFuture;
20 |
21 | /**
22 | * If-then-else or if-then (if elseFuture is null), optimized for thread execution. The task also serves as its
23 | * own {@link Binding} to reduce object creation for this built-in case.
24 | *
25 | * @author Brendan McCarthy
26 | */
27 | class TernaryConditionalTask extends ConditionalTask implements TaskInterface> {
28 | final BascomTaskFuture thenFuture;
29 | final BascomTaskFuture elseFuture;
30 | final boolean thenActivate;
31 | final boolean elseActivate;
32 |
33 | TernaryConditionalTask(Engine engine,
34 | CompletableFuture condition,
35 | CompletableFuture thenValue, boolean thenActivate,
36 | CompletableFuture elseValue, boolean elseActivate) {
37 | super(engine,condition);
38 | this.thenFuture = ensureWrapped(thenValue, false);
39 | this.elseFuture = ensureWrapped(elseValue, false);
40 | this.thenActivate = thenActivate;
41 | this.elseActivate = elseActivate;
42 | }
43 |
44 | @Override
45 | public TaskInterface> getTask() {
46 | return this;
47 | }
48 |
49 | /**
50 | * Always activate the condition, and also active then/else futures if requested.
51 | *
52 | * @param pending to be processed
53 | * @return pending
54 | */
55 | @Override
56 | Binding> doActivate(Binding> pending, TimeBox timeBox) {
57 | pending = condition.activate(this, pending, timeBox);
58 | pending = activateIf(pending, thenFuture, thenActivate, timeBox);
59 | pending = activateIf(pending, elseFuture, elseActivate, timeBox);
60 | return pending;
61 | }
62 |
63 | @Override
64 | protected Object invokeTaskMethod() {
65 | if (get(condition)) {
66 | ensureActivated(thenFuture);
67 | return thenFuture;
68 | } else {
69 | ensureActivated(elseFuture);
70 | return elseFuture;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TimeBox.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import com.ebay.bascomtask.exceptions.TimeoutExceededException;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 |
23 | import java.util.ArrayList;
24 | import java.util.List;
25 |
26 | /**
27 | * Records user-requested timeout durations and detects when that timeout has been exceeded.
28 | *
29 | * @author Brendan McCarthy
30 | */
31 | class TimeBox {
32 | private static final Logger LOG = LoggerFactory.getLogger(TimeBox.class);
33 |
34 | // Static instance used for no-timeout case (timeBudget==0) since there is no need to have a unique
35 | // instance for values that will always be the same
36 | static final TimeBox NO_TIMEOUT = new TimeBox(0);
37 |
38 | // How many milliseconds before the timeout
39 | final long timeBudget;
40 |
41 | // When did the clock start, in milliseconds
42 | final long start;
43 |
44 | // Records threads to be interrupted, when the TimeoutStrategy in effect calls for interrupts
45 | private List activeThreeads = null;
46 |
47 | /**
48 | * A timeBudget of zero means no timeout check will later be made.
49 | *
50 | * @param timeBudget to (later) check for
51 | */
52 | TimeBox(long timeBudget) {
53 | this.timeBudget = timeBudget;
54 | this.start = System.currentTimeMillis();
55 | }
56 |
57 | @Override
58 | public String toString() {
59 | if (timeBudget == 0) {
60 | return "TimeBox(0)";
61 | } else {
62 | long left = (start + timeBudget) - System.currentTimeMillis();
63 | String msg = isTimedOut() ? "EXCEEDED" : (left + "ms left");
64 | return "TimeBox(" + start + "," + timeBudget + ',' + msg + ')';
65 | }
66 | }
67 |
68 | private boolean isTimedOut() {
69 | // Apply gt here rather than gte since in some spawnmodes we get to this point very quickly
70 | return System.currentTimeMillis() > start + timeBudget;
71 | }
72 |
73 | void checkIfTimeoutExceeded(Binding> binding) {
74 | if (timeBudget > 0 && isTimedOut()) {
75 | String msg = "Timeout " + timeBudget + " exceeded before " + binding.getTaskPlusMethodName() + ", ceasing task creation";
76 | LOG.debug("Throwing " + msg);
77 | throw new TimeoutExceededException(msg);
78 | }
79 | }
80 |
81 | void checkForInterruptsNeeded(Binding> binding) {
82 | if (timeBudget > 0) {
83 | if (binding.engine.getTimeoutStrategy() == TimeoutStrategy.INTERRUPT_AT_NEXT_OPPORTUNITY) {
84 | if (isTimedOut()) {
85 | interruptRegisteredThreads();
86 | }
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * Interrupts any currently-registered thread.
93 | */
94 | void interruptRegisteredThreads() {
95 | synchronized (this) {
96 | if (activeThreeads != null) {
97 | int count = activeThreeads.size() - 1; // Exclude current thread
98 | if (count > 0) {
99 | String msg = " on timeout " + timeBudget + " exceeded";
100 | for (Thread next : activeThreeads) {
101 | if (next != Thread.currentThread()) {
102 | LOG.debug("Interrupting " + next.getName() + msg);
103 | next.interrupt();
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | /**
112 | * Register current thread so that it may later be interrupted on timeout.
113 | *
114 | * @param orchestrator active
115 | */
116 | void register(Orchestrator orchestrator) {
117 | if (orchestrator.getTimeoutStrategy() != TimeoutStrategy.PREVENT_NEW) {
118 | synchronized (this) {
119 | if (activeThreeads == null) {
120 | activeThreeads = new ArrayList<>();
121 | }
122 | activeThreeads.add(Thread.currentThread());
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * De-registers current thead and ensures monitoring thread, if present is notified if there are no
129 | * more registered threads. This call must follow every {@link #register(Orchestrator)} call.
130 | */
131 | synchronized void deregister() {
132 | if (activeThreeads != null) {
133 | activeThreeads.remove(Thread.currentThread());
134 | if (activeThreeads.size() == 0) {
135 | notify();
136 | }
137 | }
138 | }
139 |
140 | /**
141 | * Sets up a monitoring thread if needed.
142 | *
143 | * @param orchestrator context
144 | */
145 | void monitorIfNeeded(Orchestrator orchestrator) {
146 | if (orchestrator.getTimeoutStrategy() == TimeoutStrategy.INTERRUPT_IMMEDIATELY) {
147 | orchestrator.getExecutorService().execute(() -> {
148 | synchronized (this) {
149 | try {
150 | wait(timeBudget);
151 | interruptRegisteredThreads();
152 | } catch (InterruptedException ignore) {
153 | // do nothing
154 | }
155 | }
156 | });
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TimeoutStrategy.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.util.concurrent.TimeUnit;
20 |
21 | /**
22 | * Strategies for dealing with execution timeouts that impact threads spawned during the execution of a request
23 | * such as {@link java.util.concurrent.CompletableFuture#get(long, TimeUnit)}. The minimum is{@link #PREVENT_NEW},
24 | * which always applies and is the default. The other strategies indicate whether or not to interrupt any other
25 | * threads spawned by that request. As is standard with Java, if and how any user task POJO responds to an
26 | * interrupt is completely with the control of that task POJO.
27 | *
28 | * @author Brendan McCarthy
29 | */
30 | public enum TimeoutStrategy {
31 | /**
32 | * Once a timeout occurs, don't start new tasks but don't interrupt existing tasks. Default strategy.
33 | */
34 | PREVENT_NEW,
35 |
36 | /**
37 | * Once a timeout occurs, in addition to {@link #PREVENT_NEW}, the next time any task is attempted then
38 | * interrupt all others.
39 | */
40 | INTERRUPT_AT_NEXT_OPPORTUNITY,
41 |
42 | /**
43 | * Once a timeout occurs, in addition to {@link #PREVENT_NEW}, immediately interrupt all tasks. This is
44 | * the strongest reaction, and incurs the expense spawning a dedicated thread to watch for the timeout.
45 | */
46 | INTERRUPT_IMMEDIATELY
47 | }
48 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/TriConsumer.java:
--------------------------------------------------------------------------------
1 | package com.ebay.bascomtask.core;
2 |
3 | /**
4 | * Three-arg variant of {@link java.util.function.BiConsumer}.
5 | *
6 | * @param the type of the first argument to the function
7 | * @param the type of the second argument to the function
8 | * @param the type of the third argument to the function
9 | */
10 | @FunctionalInterface
11 | public interface TriConsumer {
12 |
13 | /**
14 | * Applies this function to the given arguments.
15 | *
16 | * @param t the first function argument
17 | * @param u the second function argument
18 | * @param v the third function argument
19 | */
20 | void apply(T t, U u, V v);
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/core/Utils.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.lang.annotation.Annotation;
20 | import java.lang.reflect.Method;
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | /**
24 | * Assorted utilities.
25 | *
26 | * @author Brendan McCarthy
27 | */
28 | class Utils {
29 | static T getAnnotation(Object x, Method method, Class annotationType) {
30 | if (x != null) {
31 | Class> clazz = x.getClass();
32 | try {
33 | return getAnnotation(clazz, method, annotationType);
34 | } catch (NoSuchMethodException e) {
35 | return null;
36 | } catch (Exception e) {
37 | throw new RuntimeException("Unable to read annotations", e);
38 | }
39 | }
40 | return null;
41 | }
42 |
43 | private static T getAnnotation(Class> clazz, Method method, Class annotationType) throws NoSuchMethodException {
44 | if (clazz != null) {
45 | Method localMethod = clazz.getMethod(method.getName(), method.getParameterTypes());
46 | T result = localMethod.getAnnotation(annotationType);
47 | if (result != null) {
48 | return result;
49 | } else {
50 | Class> superclass = clazz.getSuperclass();
51 | return getAnnotation(superclass, method, annotationType);
52 | }
53 | }
54 | return null;
55 | }
56 |
57 | /**
58 | * Formats the supplied arguments, in parens, after the supplied base. CompletableFutures
59 | * have a '+' or '-' prefix indicating whether or not they are complete (if not complete
60 | * there is no value, so only '-' will be displayed).
61 | *
62 | * @param sb to add to
63 | * @param base prefix
64 | * @param args arguments
65 | */
66 | static void formatFullSignature(StringBuilder sb, String base, Object[] args) {
67 | sb.append(base);
68 | sb.append('(');
69 | for (int i = 0; i < args.length; i++) {
70 | Object next = args[i];
71 | String pre = "";
72 | Object actual = next;
73 | if (next instanceof CompletableFuture>) {
74 | CompletableFuture> v = (CompletableFuture>) next;
75 | if (v.isDone()) {
76 | pre = "+";
77 | try {
78 | actual = v.get();
79 | } catch (Exception e) {
80 | throw new RuntimeException("Unexpected 'get' on CF exception", e);
81 | }
82 | } else {
83 | pre = "-";
84 | actual = "";
85 | }
86 | }
87 | if (i > 0) {
88 | sb.append(',');
89 | }
90 | sb.append(pre);
91 | sb.append(actual == null ? "null" : actual);
92 | }
93 | sb.append(')');
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/exceptions/InvalidTaskException.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.exceptions;
18 |
19 | /**
20 | * Applies to a task that cannot be processed by the framework.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public class InvalidTaskException extends RuntimeException {
25 |
26 | public InvalidTaskException(String msg) {
27 | super(msg);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/exceptions/InvalidTaskMethodException.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.exceptions;
18 |
19 | /**
20 | * Applies to a task that cannot method be executed by the framework.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public class InvalidTaskMethodException extends RuntimeException {
25 |
26 | public InvalidTaskMethodException(String msg) {
27 | super(msg);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/exceptions/MisplacedTaskMethodException.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.exceptions;
18 |
19 | import com.ebay.bascomtask.core.TaskInterface;
20 |
21 | /**
22 | * Thrown when a {@link com.ebay.bascomtask.core.TaskInterface} method is called directly on a user POJO
23 | * rather than the wrapper return from {@link com.ebay.bascomtask.core.Orchestrator#task(TaskInterface)},
24 | * when the user POJO has not overridden such methods (which is usually the case).
25 | *
26 | * @author Brendan McCarthy
27 | */
28 | public class MisplacedTaskMethodException extends RuntimeException {
29 |
30 | public MisplacedTaskMethodException(TaskInterface> task, String method) {
31 | super(format(task,method));
32 | }
33 |
34 | private static String format(TaskInterface> task, String method) {
35 | return String.format(
36 | "Method %s.%s not overwritten in task subclass, so this method can only be applied on"
37 | + " task wrappers obtained from Orchestrator.task()",
38 | task.getClass().getSimpleName(),method);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/exceptions/TaskNotStartedException.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.exceptions;
18 |
19 | /**
20 | * Applies to a task that was never started because of an exception elsewhere.
21 | *
22 | * @author Brendan McCarthy
23 | */
24 | public class TaskNotStartedException extends RuntimeException {
25 | public TaskNotStartedException(Throwable t) {
26 | super(t);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/exceptions/TimeoutExceededException.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.exceptions;
18 |
19 | /**
20 | * When a timeout has been specified and exceeded. This is used by framework instead of
21 | * {@link java.util.concurrent.TimeoutException} because that is a checked exception and setting
22 | * the timeout in BascomTask is optional so it would tedious to force callers to handle it.
23 | *
24 | * @author Brendan McCarthy
25 | */
26 | public class TimeoutExceededException extends RuntimeException {
27 |
28 | public TimeoutExceededException(String msg) {
29 | super(msg);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/runners/LogTaskLevel.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.runners;
18 |
19 | import org.slf4j.Logger;
20 |
21 | /**
22 | * Provides missing log levels for slf4j.
23 | *
24 | * @author Brendan McCarthy
25 | */
26 | public enum LogTaskLevel {
27 | INFO {
28 | public boolean isEnabled(Logger logger) {
29 | return logger.isInfoEnabled();
30 | }
31 |
32 | public void write(Logger logger, String msg) {
33 | logger.info(msg);
34 | }
35 |
36 | public void write(Logger logger, String format, Object arg) {
37 | logger.info(format, arg);
38 | }
39 |
40 | public void write(Logger logger, String format, Object arg1, Object arg2) {
41 | logger.info(format, arg1, arg2);
42 | }
43 |
44 | public void write(Logger logger, String format, Object... arguments) {
45 | logger.info(format, arguments);
46 | }
47 |
48 | public void write(Logger logger, String msg, Throwable t) {
49 | logger.info(msg, t);
50 | }
51 | },
52 | ERROR {
53 | public boolean isEnabled(Logger logger) {
54 | return logger.isErrorEnabled();
55 | }
56 |
57 | public void write(Logger logger, String msg) {
58 | logger.error(msg);
59 | }
60 |
61 | public void write(Logger logger, String format, Object arg) {
62 | logger.error(format, arg);
63 | }
64 |
65 | public void write(Logger logger, String format, Object arg1, Object arg2) {
66 | logger.error(format, arg1, arg2);
67 | }
68 |
69 | public void write(Logger logger, String format, Object... arguments) {
70 | logger.error(format, arguments);
71 | }
72 |
73 | public void write(Logger logger, String msg, Throwable t) {
74 | logger.error(msg, t);
75 | }
76 | },
77 | WARN {
78 | public boolean isEnabled(Logger logger) {
79 | return logger.isWarnEnabled();
80 | }
81 |
82 | public void write(Logger logger, String msg) {
83 | logger.warn(msg);
84 | }
85 |
86 | public void write(Logger logger, String format, Object arg) {
87 | logger.warn(format, arg);
88 | }
89 |
90 | public void write(Logger logger, String format, Object arg1, Object arg2) {
91 | logger.warn(format, arg1, arg2);
92 | }
93 |
94 | public void write(Logger logger, String format, Object... arguments) {
95 | logger.warn(format, arguments);
96 | }
97 |
98 | public void write(Logger logger, String msg, Throwable t) {
99 | logger.warn(msg, t);
100 | }
101 | },
102 | TRACE {
103 | public boolean isEnabled(Logger logger) {
104 | return logger.isTraceEnabled();
105 | }
106 |
107 | public void write(Logger logger, String msg) {
108 | logger.trace(msg);
109 | }
110 |
111 | public void write(Logger logger, String format, Object arg) {
112 | logger.trace(format, arg);
113 | }
114 |
115 | public void write(Logger logger, String format, Object arg1, Object arg2) {
116 | logger.trace(format, arg1, arg2);
117 | }
118 |
119 | public void write(Logger logger, String format, Object... arguments) {
120 | logger.trace(format, arguments);
121 | }
122 |
123 | public void write(Logger logger, String msg, Throwable t) {
124 | logger.trace(msg, t);
125 | }
126 | },
127 | DEBUG {
128 | public boolean isEnabled(Logger logger) {
129 | return logger.isDebugEnabled();
130 | }
131 |
132 | public void write(Logger logger, String msg) {
133 | logger.debug(msg);
134 | }
135 |
136 | public void write(Logger logger, String format, Object arg) {
137 | logger.debug(format, arg);
138 | }
139 |
140 | public void write(Logger logger, String format, Object arg1, Object arg2) {
141 | logger.debug(format, arg1, arg2);
142 | }
143 |
144 | public void write(Logger logger, String format, Object... arguments) {
145 | logger.debug(format, arguments);
146 | }
147 |
148 | public void write(Logger logger, String msg, Throwable t) {
149 | logger.debug(msg, t);
150 | }
151 | };
152 |
153 | public abstract boolean isEnabled(Logger logger);
154 |
155 | public abstract void write(Logger logger, String msg);
156 |
157 | public abstract void write(Logger logger, String format, Object arg);
158 |
159 | public abstract void write(Logger logger, String format, Object arg1, Object arg2);
160 |
161 | public abstract void write(Logger logger, String format, Object... arguments);
162 |
163 | public abstract void write(Logger logger, String msg, Throwable t);
164 | }
165 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/runners/LogTaskRunner.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.runners;
18 |
19 | import com.ebay.bascomtask.core.TaskRun;
20 | import com.ebay.bascomtask.core.TaskRunner;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 |
25 | /**
26 | * Logs task execution before and after, with a configurable level and detail. A completion message is
27 | * also logged if the completion (of a CompletableFuture) occurs after message exit.
28 | *
29 | * @author Brendan McCarthy
30 | */
31 | public class LogTaskRunner implements TaskRunner {
32 | private static final Logger LOG = LoggerFactory.getLogger(LogTaskRunner.class);
33 |
34 | private LogTaskLevel level = LogTaskLevel.DEBUG;
35 | private boolean full = false;
36 |
37 | /**
38 | * Set debug level. This does not configure the level, which must be done in any slf4j-conformant manner.
39 | *
40 | * @param level to use
41 | */
42 | public void setLevel(LogTaskLevel level) {
43 | this.level = level;
44 | }
45 |
46 | /**
47 | * Makes log output include actual method arguments.
48 | *
49 | * @param full include method arguments or not
50 | */
51 | public void setFullSignatureLogging(boolean full) {
52 | this.full = full;
53 | }
54 |
55 | private String detail(TaskRun taskRun) {
56 | if (full) {
57 | StringBuilder sb = new StringBuilder();
58 | taskRun.formatActualSignature(sb);
59 | return sb.toString();
60 | } else {
61 | return taskRun.getTaskPlusMethodName();
62 | }
63 | }
64 |
65 | @Override
66 | public Object before(TaskRun taskRun) {
67 | return level.isEnabled(LOG) ? detail(taskRun) : null;
68 | }
69 |
70 | @Override
71 | public Object executeTaskMethod(TaskRun taskRun, Thread parentThread, Object fromBefore) {
72 | final String state = parentThread != Thread.currentThread() ? "(spawned)" : "";
73 | if (level.isEnabled(LOG)) {
74 | level.write(LOG, "BEGIN-TASK {} {}", fromBefore, state);
75 | }
76 | try {
77 | return taskRun.run();
78 | } finally {
79 | if (level.isEnabled(LOG)) {
80 | level.write(LOG, "END-TASK {} {}", fromBefore, state);
81 | }
82 | }
83 | }
84 |
85 | @Override
86 | public void onComplete(TaskRun taskRun, Object fromBefore, boolean doneOnExit) {
87 | if (level.isEnabled(LOG) && !doneOnExit) {
88 | level.write(LOG, "COMPLETE-TASK {}", fromBefore);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/bascomtask-core/src/main/java/com/ebay/bascomtask/runners/StatTaskRunner.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.runners;
18 |
19 | import com.ebay.bascomtask.core.TaskRun;
20 | import com.ebay.bascomtask.core.TaskRunner;
21 |
22 | import java.io.ByteArrayOutputStream;
23 | import java.io.PrintStream;
24 | import java.io.UnsupportedEncodingException;
25 | import java.nio.charset.StandardCharsets;
26 | import java.util.Arrays;
27 | import java.util.HashMap;
28 | import java.util.List;
29 | import java.util.Map;
30 | import java.util.function.Consumer;
31 | import java.util.function.Function;
32 |
33 | /**
34 | * Collects various task execution statistics.
35 | *
36 | * @author Brendan McCarthy
37 | */
38 | public class StatTaskRunner implements TaskRunner {
39 | private final Map map = new HashMap<>();
40 |
41 | private static class InternalTiming {
42 | private long maxExecTime = 0;
43 | private long minExecTime = Long.MAX_VALUE;
44 | private long execTotal = 0;
45 |
46 | double avg(int count) {
47 | return count == 0 ? 0 : execTotal / (double) count;
48 | }
49 |
50 | void add(long duration) {
51 | execTotal += duration;
52 | minExecTime = Math.min(minExecTime, duration);
53 | maxExecTime = Math.max(maxExecTime, duration);
54 | }
55 | }
56 |
57 | private static class InternalStat {
58 | private int count = 0;
59 | final InternalTiming execTime = new InternalTiming();
60 | final InternalTiming completionTime = new InternalTiming();
61 |
62 | void add(long duration) {
63 | count++;
64 | execTime.add(duration);
65 | }
66 | }
67 |
68 | private synchronized void add(String key, long duration) {
69 | InternalStat stat = map.computeIfAbsent(key, k -> new InternalStat());
70 | stat.add(duration);
71 | }
72 |
73 | private synchronized void extend(String key, long duration) {
74 | InternalStat stat = map.computeIfAbsent(key, k -> new InternalStat());
75 | stat.completionTime.add(duration);
76 | }
77 |
78 | @Override
79 | public Object before(TaskRun taskRun) {
80 | return null;
81 | }
82 |
83 | @Override
84 | public Object executeTaskMethod(TaskRun taskRun, Thread parentThread, Object fromBefore) {
85 | return taskRun.run();
86 | }
87 |
88 | @Override
89 | public void onComplete(TaskRun taskRun, Object fromBefore, boolean doneOnExit) {
90 | long duration = taskRun.getEndedAt() - taskRun.getStartedAt();
91 | add(taskRun.getTaskPlusMethodName(), duration);
92 | if (!doneOnExit) {
93 | duration = taskRun.getCompletedAt() - taskRun.getEndedAt();
94 | extend(taskRun.getTaskPlusMethodName(), duration);
95 | }
96 | }
97 |
98 | private static int width(long number) {
99 | int length = 1;
100 | if (number >= 100000000) {
101 | length += 8;
102 | number /= 100000000;
103 | }
104 | if (number >= 10000) {
105 | length += 4;
106 | number /= 10000;
107 | }
108 | if (number >= 100) {
109 | length += 2;
110 | number /= 100;
111 | }
112 | if (number >= 10) {
113 | length += 1;
114 | }
115 | return length;
116 | }
117 |
118 | public static class Timing {
119 | public long average;
120 | public long max;
121 | public long min;
122 |
123 | void populateFrom(InternalTiming internalTiming, int count) {
124 | this.average = Math.round(internalTiming.avg(count));
125 | this.max = internalTiming.maxExecTime;
126 | this.min = internalTiming.minExecTime;
127 | }
128 | }
129 |
130 | public static class Stat {
131 | public String taskMethod;
132 | public long count;
133 | public Timing execTime;
134 | public Timing completionTime;
135 | }
136 |
137 | public static class Report {
138 | Stat[] stats;
139 | }
140 |
141 | /**
142 | * Returns a summarized execution data snapshot.
143 | *
144 | * @return data
145 | */
146 | public synchronized Report collect() {
147 | Report report = new Report();
148 | int sz = map.size();
149 | report.stats = new Stat[sz];
150 | int pos = 0;
151 | for (Map.Entry next : map.entrySet()) {
152 | Stat stat = report.stats[pos++] = new Stat();
153 | stat.taskMethod = next.getKey();
154 | InternalStat internalStat = next.getValue();
155 | stat.count = internalStat.count;
156 | stat.execTime = new Timing();
157 | stat.execTime.populateFrom(internalStat.execTime, internalStat.count);
158 | if (internalStat.completionTime.maxExecTime > 0) {
159 | stat.completionTime = new Timing();
160 | stat.completionTime.populateFrom(internalStat.completionTime, internalStat.count);
161 | }
162 | }
163 | return report;
164 | }
165 |
166 | private static void fill(PrintStream ps, char c, int count) {
167 | for (int i = 0; i < count; i++) {
168 | ps.print(c);
169 | }
170 | }
171 |
172 | /**
173 | * A table column.
174 | */
175 | private static abstract class Col {
176 | final String hdr;
177 | int maxWidth;
178 |
179 | Col(String hdr) {
180 | this.hdr = hdr;
181 | maxWidth = hdr.length();
182 | }
183 |
184 | void widen(Stat stat) {
185 | maxWidth = Math.max(maxWidth, getValueWidth(stat));
186 |
187 | }
188 |
189 | void hdr(PrintStream ps) {
190 | int len = hdr.length();
191 | int fill = Math.max(0, maxWidth - len);
192 | int before = fill / 2;
193 | int after = fill - before;
194 | fill(ps, ' ', before + 1);
195 | ps.print(hdr);
196 | fill(ps, ' ', after + 1);
197 | ps.print('|');
198 | }
199 |
200 | void sep(PrintStream ps, char c) {
201 | fill(ps, '-', maxWidth + 2);
202 | ps.print(c);
203 | }
204 |
205 | abstract int getValueWidth(Stat stat);
206 |
207 | abstract void print(PrintStream ps, Stat stat);
208 |
209 | void cell(PrintStream ps, Stat stat) {
210 | int width = getValueWidth(stat);
211 | print(ps, stat);
212 | fill(ps, ' ', 2 + maxWidth - width);
213 | ps.print('|');
214 | }
215 | }
216 |
217 | private static class LongCol extends Col {
218 | private final Function fn;
219 |
220 | LongCol(String hdr, Function fn) {
221 | super(hdr);
222 | this.fn = fn;
223 | }
224 |
225 | @Override
226 | int getValueWidth(Stat stat) {
227 | return width(fn.apply(stat));
228 | }
229 |
230 | @Override
231 | void print(PrintStream ps, Stat stat) {
232 | ps.print(fn.apply(stat));
233 | }
234 | }
235 |
236 | static class StringCol extends Col {
237 | private final Function fn;
238 |
239 | StringCol(String hdr, Function fn) {
240 | super(hdr);
241 | this.fn = fn;
242 | }
243 |
244 | @Override
245 | int getValueWidth(Stat stat) {
246 | return fn.apply(stat).length();
247 | }
248 |
249 | @Override
250 | void print(PrintStream ps, Stat stat) {
251 | ps.print(fn.apply(stat));
252 | }
253 | }
254 |
255 | private static class AddCol extends Col {
256 | private final Function fn;
257 |
258 | AddCol(String hdr, Function fn) {
259 | super(hdr);
260 | this.fn = fn;
261 | }
262 |
263 | @Override
264 | int getValueWidth(Stat stat) {
265 | int w = width(fn.apply(stat.execTime));
266 | if (stat.completionTime != null) {
267 | w += width(fn.apply(stat.completionTime)) + 1;
268 | }
269 | return w;
270 | }
271 |
272 | @Override
273 | void print(PrintStream ps, Stat stat) {
274 | ps.print(fn.apply(stat.execTime));
275 | if (stat.completionTime != null) {
276 | ps.print('+');
277 | ps.print(fn.apply(stat.completionTime));
278 | }
279 | }
280 | }
281 |
282 | private void row(PrintStream ps, List cols, char div, Consumer fn) {
283 | ps.print(div);
284 | cols.forEach(fn);
285 | ps.print('\n');
286 | }
287 |
288 | /**
289 | * Prints a tabular-formatted summary of statistics to the given PrintStream.
290 | *
291 | * @param ps to print to
292 | */
293 | public void report(PrintStream ps) {
294 | Report report = collect();
295 |
296 | List cols = Arrays.asList(
297 | new LongCol("Count", stat -> stat.count),
298 | new AddCol("Avg", timing -> timing.average),
299 | new AddCol("Min", timing -> timing.min),
300 | new AddCol("Max", timing -> timing.max),
301 | new StringCol("Method", stat -> stat.taskMethod)
302 | );
303 |
304 | for (Stat next : report.stats) {
305 | cols.forEach(col -> col.widen(next));
306 | }
307 |
308 | row(ps, cols, '-', col -> col.sep(ps, '-'));
309 | row(ps, cols, '|', col -> col.hdr(ps));
310 | row(ps, cols, '|', col -> col.sep(ps, '|'));
311 |
312 | for (Stat next : report.stats) {
313 | row(ps, cols, '|', col -> col.cell(ps, next));
314 | }
315 |
316 | row(ps, cols, '-', col -> col.sep(ps, '-'));
317 | }
318 |
319 | /**
320 | * Returns a table-formatted summary as a string.
321 | *
322 | * @return table summary
323 | */
324 | public String report() {
325 | final String ENC = StandardCharsets.UTF_8.name();
326 | try {
327 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
328 | PrintStream ps = new PrintStream(baos, true, ENC);
329 | report(ps);
330 | ps.flush();
331 | return baos.toString(ENC);
332 | } catch (UnsupportedEncodingException e) {
333 | throw new RuntimeException("Bad encoding", e);
334 | }
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/FullTestSuite.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask;
18 |
19 | import com.ebay.bascomtask.core.*;
20 | import com.ebay.bascomtask.runners.LogTaskRunnerTest;
21 | import com.ebay.bascomtask.runners.ProfilingTaskRunnerTest;
22 | import com.ebay.bascomtask.runners.StatTaskRunnerTest;
23 | import com.ebay.bascomtask.timings.TimingTest;
24 | import org.junit.runner.RunWith;
25 | import org.junit.runners.Suite;
26 |
27 | @RunWith(Suite.class)
28 | @Suite.SuiteClasses({
29 | UtilsTest.class,
30 | CoreTest.class,
31 | BascomTaskFutureTest.class,
32 | FnTaskTest.class,
33 | TaskRunnerTest.class,
34 | LogTaskRunnerTest.class,
35 | ProfilingTaskRunnerTest.class,
36 | StatTaskRunnerTest.class,
37 | TimingTest.class,
38 | AccessTest.class,
39 | OrchestratorPassingTest.class,
40 | TaskVariationsTest.class
41 |
42 | })
43 | public class FullTestSuite {
44 | }
45 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/core/AccessTest.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import org.junit.Test;
20 |
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | import static com.ebay.bascomtask.core.TaskVariations.*;
24 | import static org.junit.Assert.assertEquals;
25 |
26 | /**
27 | * Tests tasks without public access.
28 | *
29 | * @author Brendan McCarthy
30 | */
31 | public class AccessTest extends BaseOrchestratorTest {
32 |
33 | interface INotPublic extends TaskInterface {
34 | CompletableFuture getSomething();
35 | }
36 |
37 | private static class NotPublic implements INotPublic {
38 | @Override
39 | public CompletableFuture getSomething() {
40 | return complete(1);
41 | }
42 | }
43 |
44 | @Test
45 | public void access() throws Exception {
46 | Orchestrator $ = Orchestrator.create();
47 | CompletableFuture x = $.task(new NotPublic()).getSomething();
48 | assertEquals(1,(int)x.join());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/core/BascomTaskFutureTest.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import org.junit.After;
20 | import org.junit.Test;
21 |
22 | import java.util.concurrent.CompletableFuture;
23 | import java.util.concurrent.Executor;
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 |
27 | import static org.junit.Assert.*;
28 |
29 | /**
30 | * Test CompletableFutures activation.
31 | *
32 | * @author Brendan McCarthy
33 | */
34 | public class BascomTaskFutureTest extends BaseOrchestratorTest {
35 |
36 | private static final Executor executor = Executors.newFixedThreadPool(5);
37 | private CountingTask.Impl currentTask;
38 |
39 | private interface CountingTask extends TaskInterface {
40 | CompletableFuture rets(String s);
41 |
42 | CompletableFuture ret(int v);
43 |
44 | CompletableFuture retHit(int v);
45 |
46 | CompletableFuture fault(CompletableFuture cf);
47 |
48 | CompletableFuture faults(CompletableFuture cf);
49 |
50 | class Impl implements CountingTask {
51 | final int exp;
52 | final AtomicInteger count = new AtomicInteger(0);
53 | final AtomicInteger holder = new AtomicInteger(0);
54 |
55 | Impl(int exp) {
56 | this.exp = exp;
57 | }
58 |
59 | public int hit() {
60 | return count.incrementAndGet();
61 | }
62 |
63 | @Override
64 | public CompletableFuture rets(String s) {
65 | return complete(s);
66 | }
67 |
68 | @Override
69 | public CompletableFuture ret(int v) {
70 | return complete(v);
71 | }
72 |
73 | @Override
74 | public CompletableFuture retHit(int v) {
75 | hit();
76 | return ret(v);
77 | }
78 |
79 | @Override
80 | public CompletableFuture fault(CompletableFuture cf) {
81 | throw new RuntimeException("Fault!!!");
82 | }
83 |
84 | @Override
85 | public CompletableFuture faults(CompletableFuture cf) {
86 | throw new RuntimeException("Fault!!!");
87 | }
88 | }
89 | }
90 |
91 | private CountingTask task(int exp) {
92 | currentTask = new CountingTask.Impl(exp);
93 | return currentTask;
94 | }
95 |
96 | private int hit() {
97 | return currentTask.hit();
98 | }
99 |
100 | private void hit(int exp, int got) {
101 | assertEquals(exp, got);
102 | hit();
103 | }
104 |
105 | private void verify(int exp, int got, Throwable ex) {
106 | assertEquals(exp, got);
107 | assertNull(ex);
108 | }
109 |
110 | @After
111 | public void after() {
112 | super.after();
113 | sleep(10); // Give time for async routines to execute
114 | if (currentTask != null) { // This being called even before tests for some reason
115 | assertEquals(currentTask.exp, currentTask.count.get());
116 | }
117 | }
118 |
119 | @Test
120 | public void toStringFormat() {
121 | final String NAME = "foo-bar-baz";
122 | CompletableFuture cf = $.task(task(0)).name(NAME).ret(0);
123 | assertTrue(cf.toString().contains(NAME));
124 | }
125 |
126 | @Test
127 | public void get() throws Exception {
128 | CompletableFuture cf = $.task(task(1)).retHit(8);
129 | assertEquals(8, (int) cf.get());
130 | }
131 |
132 | @Test
133 | public void join() {
134 | CompletableFuture cf = $.task(task(1)).retHit(8);
135 | assertEquals(8, (int) cf.join());
136 | }
137 |
138 | @Test
139 | public void getNow() {
140 | CompletableFuture cf = $.task(task(1)).retHit(8);
141 | assertEquals(8, (int) cf.getNow(99));
142 | }
143 |
144 | @Test
145 | public void thenAccept() {
146 | CompletableFuture cf = $.task(task(0)).ret(1);
147 | assertNotNull(cf.thenAccept(v -> hit()));
148 | }
149 |
150 | @Test
151 | public void thenAcceptActivate() {
152 | CompletableFuture cf = $.task(task(1)).ret(1);
153 | $.activate(cf);
154 | assertNotNull(cf.thenAccept(v -> hit()));
155 | }
156 |
157 | @Test
158 | public void thenApplyThenApply() throws Exception {
159 | CompletableFuture cf = $.task(task(2)).ret(1);
160 |
161 | CompletableFuture c2 = cf.thenApply(v -> hit() + v + 10).thenApply(v -> hit() + v + 20);
162 | $.activate(cf);
163 | assertEquals(34, (int) c2.get());
164 | }
165 |
166 | @Test
167 | public void thenApplyThenAccept() throws Exception {
168 | CompletableFuture cf = $.task(task(2)).ret(1);
169 | CompletableFuture c2 = cf.thenApply(v -> hit() + v + 10).thenAccept(v -> hit());
170 | $.activate(cf);
171 | c2.get();
172 | }
173 |
174 | @Test
175 | public void thenCombineAsyncExecutor() throws Exception {
176 | CountingTask task = task(0);
177 | CompletableFuture cf1 = $.task(task).ret(1);
178 | CompletableFuture cf2 = $.task(task).ret(1);
179 | CompletableFuture combine = cf1.thenCombineAsync(cf2, Integer::sum, executor);
180 | $.activate(cf1,cf2);
181 | int got = combine.get();
182 | assertEquals(2, got);
183 | }
184 |
185 | @Test
186 | public void thenCompose() throws Exception {
187 | final int RV = 9;
188 | CountingTask task = task(0);
189 | CompletableFuture cf1 = $.task(task).ret(1);
190 | CompletableFuture cf2 = $.task(task).ret(RV);
191 | CompletableFuture compose = cf1.thenCompose(v -> cf2);
192 | $.activate(cf1,cf2);
193 | int got = compose.get();
194 | assertEquals(RV, got);
195 | }
196 |
197 | @Test
198 | public void applyToEither() throws Exception {
199 | CountingTask task = task(2);
200 | CompletableFuture cf1 = $.task(task).retHit(1);
201 | CompletableFuture cf2 = $.task(task).retHit(1);
202 | CompletableFuture combine = cf1.applyToEither(cf2, x -> x * 5);
203 | $.activate(cf1,cf2);
204 | int got = combine.get();
205 | assertEquals(5, got);
206 | }
207 |
208 | @Test
209 | public void applyToEitherAsync() throws Exception {
210 | CountingTask task = task(2);
211 | CompletableFuture cf1 = $.task(task).retHit(1);
212 | CompletableFuture cf2 = $.task(task).retHit(1);
213 | CompletableFuture combine = cf1.applyToEitherAsync(cf2, x -> x * 5);
214 | $.activate(cf1,cf2);
215 | int got = combine.get();
216 | assertEquals(5, got);
217 | }
218 |
219 | @Test
220 | public void applyToEitherAsyncExecutor() throws Exception {
221 | CountingTask task = task(2);
222 | CompletableFuture cf1 = $.task(task).retHit(1);
223 | CompletableFuture cf2 = $.task(task).retHit(1);
224 | CompletableFuture combine = cf1.applyToEitherAsync(cf2, x -> x * 5, executor);
225 | $.activate(cf1,cf2);
226 | int got = combine.get();
227 | assertEquals(5, got);
228 | }
229 |
230 | @Test
231 | public void acceptEitherAsync() throws Exception {
232 | CountingTask task = task(2);
233 | CompletableFuture cf1 = $.task(task).retHit(1);
234 | CompletableFuture cf2 = $.task(task).retHit(1);
235 | CompletableFuture either = cf1.acceptEitherAsync(cf2, x -> currentTask.holder.set(x));
236 | $.activateAndWait(cf1,cf2);
237 | sleep(20);
238 | assertEquals(1, currentTask.holder.get());
239 | }
240 |
241 | @Test
242 | public void whenComplete() throws Exception {
243 | int expectedValue = 567;
244 | CompletableFuture cf1 = $.task(task(1)).retHit(expectedValue);
245 | cf1.join();
246 | int got = cf1.whenComplete((v, ex) -> verify(expectedValue, v, ex)).get();
247 | assertEquals(expectedValue,got);
248 | }
249 |
250 | @Test
251 | public void whenCompleteAsync() throws Exception {
252 | int expectedValue = 567;
253 | CompletableFuture cf1 = $.task(task(1)).retHit(expectedValue);
254 | cf1.join();
255 | int got = cf1.whenCompleteAsync((v, ex) -> verify(expectedValue, v, ex)).get();
256 | assertEquals(expectedValue,got);
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/core/BaseOrchestratorTest.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import org.junit.Before;
20 | import org.junit.Rule;
21 | import org.junit.rules.TestName;
22 | import org.slf4j.Logger;
23 | import org.slf4j.LoggerFactory;
24 |
25 | public abstract class BaseOrchestratorTest {
26 | private static final Logger LOG = LoggerFactory.getLogger("@Test");
27 |
28 | protected Orchestrator $;
29 |
30 | @Rule
31 | public final TestName name = new TestName();
32 |
33 | @Before
34 | public void before() {
35 | GlobalOrchestratorConfig.getConfig().restoreConfigurationDefaults(null);
36 | $ = Orchestrator.create();
37 | String testName = name.getMethodName();
38 | LOG.debug(testName);
39 | }
40 |
41 | @Before
42 | public void after() {
43 | GlobalOrchestratorConfig.getConfig().restoreConfigurationDefaults(null);
44 | }
45 |
46 | static void sleep(int ms) {
47 | try {
48 | Thread.sleep(ms);
49 | } catch (InterruptedException e) {
50 | throw new RuntimeException("Bad sleep", e);
51 | }
52 | }
53 |
54 | static int sleepThen(int ms, int v) {
55 | sleep(ms);
56 | return v;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/core/ExceptionTask.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import java.util.concurrent.CompletableFuture;
20 |
21 | /**
22 | * Task that always throws exceptions.
23 | *
24 | * @author Brendan Mccarthy
25 | */
26 | public interface ExceptionTask extends TaskInterface> {
27 |
28 | class FaultHappened extends RuntimeException {
29 | public FaultHappened(String msg) {
30 | super(msg);
31 | }
32 | }
33 |
34 | CompletableFuture faultImmediate(String msg);
35 |
36 | CompletableFuture faultAfter(int ms, String msg);
37 |
38 | CompletableFuture faultAfter(CompletableFuture x, int ms, String msg);
39 |
40 | CompletableFuture faultImmediateCompletion(int ms, String msg);
41 |
42 | CompletableFuture returnsNull();
43 |
44 | /**
45 | * Throws Exception after interval in external (non-BT) CompletableFuture-managed thread.
46 | *
47 | * @param ms to wait before throwing
48 | * @param msg to include in exceptino
49 | * @return !isDone() CompletableFuture
50 | */
51 | CompletableFuture faultWithCompletionAfter(int ms, String msg);
52 |
53 | static Faulty faulty() {
54 | return new Faulty<>();
55 | }
56 |
57 | class Faulty implements ExceptionTask {
58 |
59 | private CompletableFuture fault(int ms, String msg) {
60 | try {
61 | Thread.sleep(ms);
62 | } catch (InterruptedException e) {
63 | throw new RuntimeException("Bad interrupt", e);
64 | }
65 | throw new FaultHappened(msg);
66 | }
67 |
68 | private RET delayFault(int ms, String msg) {
69 | try {
70 | Thread.sleep(ms);
71 | } catch (InterruptedException e) {
72 | throw new RuntimeException("Bad interrupt", e);
73 | }
74 | throw new FaultHappened(msg);
75 | }
76 |
77 | private CompletableFuture faultWithCompletion(int ms, String msg) {
78 | return CompletableFuture.supplyAsync(() -> delayFault(ms, msg));
79 | }
80 |
81 | @Override
82 | public CompletableFuture faultImmediate(String msg) {
83 | System.out.println("FI: " + Thread.currentThread().getName());
84 | throw new FaultHappened(msg);
85 | }
86 |
87 | @Override
88 | public CompletableFuture faultAfter(int ms, String msg) {
89 | return fault(ms, msg);
90 | }
91 |
92 | @Override
93 | public CompletableFuture faultAfter(CompletableFuture x, int ms, String msg) {
94 | return fault(ms, msg);
95 | }
96 |
97 | @Override
98 | public CompletableFuture faultImmediateCompletion(int ms, String msg) {
99 | return CompletableFuture.supplyAsync(() -> {
100 | throw new FaultHappened(msg);
101 | });
102 | }
103 |
104 | @Override
105 | public CompletableFuture returnsNull() {
106 | return null; // Not valid and should raise Exception
107 | }
108 |
109 | @Override
110 | public CompletableFuture faultWithCompletionAfter(int ms, String msg) {
111 | return faultWithCompletion(ms, msg);
112 | }
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/bascomtask-core/src/test/java/com/ebay/bascomtask/core/FnTaskTest.java:
--------------------------------------------------------------------------------
1 | /*-**********************************************************************
2 | Copyright 2018 eBay Inc.
3 | Author/Developer: Brendan McCarthy
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | https://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | **************************************************************************/
17 | package com.ebay.bascomtask.core;
18 |
19 | import org.junit.Test;
20 |
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.concurrent.atomic.AtomicInteger;
23 |
24 | import static com.ebay.bascomtask.core.UberTask.task;
25 | import static org.junit.Assert.*;
26 |
27 | /**
28 | * Function task tests
29 | *
30 | * @author Brendan McCarthy
31 | */
32 | public class FnTaskTest extends BaseOrchestratorTest {
33 |
34 | private static class Simple {
35 | int input = 0;
36 |
37 | int produce() {
38 | return ++input;
39 | }
40 |
41 | void consume(int v) {
42 | input = v;
43 | }
44 |
45 | int negate(int v) {
46 | input = v;
47 | return -v;
48 | }
49 | }
50 |
51 | @Test
52 | public void naming() {
53 | SupplierTask task = $.fnTask(() -> 1);
54 | String nm = "foo";
55 | assertEquals(nm,task.name(nm).getName());
56 | }
57 |
58 | @Test
59 | public void oneTask() throws Exception {
60 | CompletableFuture cf = $.fn(() -> 1);
61 | int got = cf.get();
62 | assertEquals(1, got);
63 | }
64 |
65 | @Test
66 | public void oneTaskGet() throws Exception {
67 | int got = $.fn(() -> 1).get();
68 | assertEquals(1, got);
69 | }
70 |
71 | @Test
72 | public void oneTaskDelayedUntilGet() throws Exception {
73 | Simple simple = new Simple();
74 | CompletableFuture cf = $.fn(simple::produce);
75 | assertEquals(1, simple.input);
76 | int got = cf.get();
77 | assertEquals(1, simple.input);
78 | assertEquals(1, got);
79 | }
80 |
81 | @Test
82 | public void twoTasks() throws Exception {
83 | CompletableFuture t1 = $.fn(() -> 1);
84 | CompletableFuture t2 = $.fn(t1, x -> x + 1);
85 | int got = t2.get();
86 | assertEquals(2, got);
87 | }
88 |
89 | @Test
90 | public void applySupplierFn() throws Exception {
91 | CompletableFuture t1 = $.fn(() -> 5);
92 | CompletableFuture t2 = $.fn(t1,()->3, Integer::sum);
93 | assertEquals(8,(int)t2.get());
94 | }
95 |
96 | @Test
97 | public void supplierConsumer() throws Exception {
98 | AtomicInteger i = new AtomicInteger(0);
99 | //$.vfn(()->3, i::set);
100 | CompletableFuture future = $.vfn(()->3, x->{
101 | System.out.println("HERE");
102 | i.set(x);
103 | });
104 | $.activate(future);
105 | assertEquals(3,i.get());
106 | }
107 |
108 | @Test
109 | public void futureFunction() throws Exception {
110 | CompletableFuture t1 = $.fn(() -> 3);
111 | CompletableFuture task = $.fn(
112 | t1,
113 | x -> x * 2);
114 |
115 | int got = task.get();
116 | assertEquals(6, got);
117 | }
118 |
119 | @Test
120 | public void threeTasks() throws Exception {
121 | CompletableFuture t1 = $.fnTask(() -> 2).name("task0").apply();
122 | CompletableFuture t2 = $.fnTask(t1, x -> x + 3).name("task1").apply();
123 | CompletableFuture t3 = $.fnTask(t1, t2, (x, y) -> x * y).name("task2").apply();
124 | int got = t3.get();
125 | assertEquals(10, got);
126 | }
127 |
128 | @Test
129 | public void supSupBiFunction() throws Exception {
130 | CompletableFuture task = $.fn(
131 | () -> 2,
132 | () -> 3,
133 | (x, y) -> x * y);
134 |
135 | int got = task.get();
136 | assertEquals(6, got);
137 | }
138 |
139 | @Test
140 | public void supFutureBiFunction() throws Exception {
141 | CompletableFuture t2 = $.fn(() -> 3);
142 | CompletableFuture task = $.fn(
143 | () -> 2,
144 | t2,
145 | (x, y) -> x * y);
146 |
147 | int got = task.get();
148 | assertEquals(6, got);
149 | }
150 |
151 | @Test
152 | public void futureSupBiFunction() throws Exception {
153 | CompletableFuture t1 = $.fn(() -> 2);
154 | CompletableFuture task = $.fn(
155 | t1,
156 | () -> 3,
157 | (x, y) -> x * y);
158 |
159 | int got = task.get();
160 | assertEquals(6, got);
161 | }
162 |
163 | @Test
164 | public void futureSupBiConsumer() throws Exception {
165 | CompletableFuture t1 = $.fn(() -> 2);
166 | Simple simple = new Simple();
167 | CompletableFuture task = $.vfn(
168 | t1,
169 | () -> 3,
170 | (x, y) -> simple.input = x + y);
171 |
172 | task.get();
173 | assertEquals(5, simple.input);
174 | }
175 |
176 | @Test
177 | public void supFutureBiConsumer() throws Exception {
178 | CompletableFuture t2 = $.fn(() -> 3);
179 | Simple simple = new Simple();
180 | CompletableFuture task = $.vfn(
181 | () -> 2,
182 | t2,
183 | (x, y) -> simple.input = x + y);
184 |
185 | task.get();
186 | assertEquals(5, simple.input);
187 | }
188 |
189 | @Test
190 | public void futureFutureBiConsumer() throws Exception {
191 | CompletableFuture t1 = $.fn(() -> 2);
192 | CompletableFuture t2 = $.fn(() -> 3);
193 | Simple simple = new Simple();
194 | CompletableFuture