├── .gitignore ├── LICENSE.txt ├── README.md ├── bascomtask-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── ebay │ │ └── bascomtask │ │ ├── annotations │ │ └── Light.java │ │ ├── core │ │ ├── ActiveManager.java │ │ ├── BascomTaskFuture.java │ │ ├── BaseFnTask.java │ │ ├── BinaryConditionalTask.java │ │ ├── Binding.java │ │ ├── CommonConfig.java │ │ ├── ConditionalTask.java │ │ ├── ConsumerTask.java │ │ ├── Engine.java │ │ ├── ExternalBinding.java │ │ ├── FateTask.java │ │ ├── GlobalOrchestratorConfig.java │ │ ├── LaneRunner.java │ │ ├── Orchestrator.java │ │ ├── PlaceHolderRunner.java │ │ ├── ReflectionBinding.java │ │ ├── SpawnMode.java │ │ ├── SupplierTask.java │ │ ├── TaskInterface.java │ │ ├── TaskMeta.java │ │ ├── TaskRun.java │ │ ├── TaskRunner.java │ │ ├── TaskWrapper.java │ │ ├── TernaryConditionalTask.java │ │ ├── TimeBox.java │ │ ├── TimeoutStrategy.java │ │ ├── TriConsumer.java │ │ └── Utils.java │ │ ├── exceptions │ │ ├── InvalidTaskException.java │ │ ├── InvalidTaskMethodException.java │ │ ├── MisplacedTaskMethodException.java │ │ ├── TaskNotStartedException.java │ │ └── TimeoutExceededException.java │ │ └── runners │ │ ├── LogTaskLevel.java │ │ ├── LogTaskRunner.java │ │ ├── ProfilingTaskRunner.java │ │ └── StatTaskRunner.java │ └── test │ ├── java │ └── com │ │ └── ebay │ │ └── bascomtask │ │ ├── FullTestSuite.java │ │ ├── core │ │ ├── AccessTest.java │ │ ├── BascomTaskFutureTest.java │ │ ├── BaseOrchestratorTest.java │ │ ├── CoreTest.java │ │ ├── ExceptionTask.java │ │ ├── FnTaskTest.java │ │ ├── IThreadTask.java │ │ ├── NamingTask.java │ │ ├── OrchestratorPassingTest.java │ │ ├── TaskInterruptedException.java │ │ ├── TaskRunnerTest.java │ │ ├── TaskVariations.java │ │ ├── TaskVariationsTest.java │ │ ├── UberTask.java │ │ └── UtilsTest.java │ │ ├── runners │ │ ├── LogTaskRunnerTest.java │ │ ├── MockTaskRun.java │ │ ├── ProfilingTaskRunnerTest.java │ │ └── StatTaskRunnerTest.java │ │ ├── timings │ │ ├── GraphVariations.java │ │ ├── LongOperationsTask.java │ │ └── TimingTest.java │ │ └── util │ │ └── CommonTestingUtils.java │ └── resources │ └── application.properties ├── doc ├── Changelog.md ├── fault_flow.svg └── thread_flow.svg ├── pom.xml └── spell.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .mvn 3 | *~ 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | .metadata 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /build/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License, Version 2.0 2 | 3 | SPDX short identifier: Apache-2.0 4 | 5 | Further resources on the Apache License 2.0 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 19 | 20 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 21 | 22 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 23 | 24 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 25 | 26 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 27 | 28 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 29 | 30 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 31 | 32 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 33 | 34 | 2. Grant of Copyright License. 35 | 36 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 37 | 38 | 3. Grant of Patent License. 39 | 40 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 41 | 42 | 4. Redistribution. 43 | 44 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 45 | 46 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 47 | You must cause any modified files to carry prominent notices stating that You changed the files; and 48 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 49 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 50 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 51 | 52 | 5. Submission of Contributions. 53 | 54 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 55 | 56 | 6. Trademarks. 57 | 58 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 59 | 60 | 7. Disclaimer of Warranty. 61 | 62 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 63 | 64 | 8. Limitation of Liability. 65 | 66 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 67 | 68 | 9. Accepting Warranty or Additional Liability. 69 | 70 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 71 | 72 | END OF TERMS AND CONDITIONS 73 | 74 | APPENDIX: How to apply the Apache License to your work 75 | 76 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 77 | 78 | Copyright [yyyy] [name of copyright owner] 79 | 80 | Licensed under the Apache License, Version 2.0 (the "License"); 81 | you may not use this file except in compliance with the License. 82 | You may obtain a copy of the License at 83 | 84 | http://www.apache.org/licenses/LICENSE-2.0 85 | 86 | Unless required by applicable law or agreed to in writing, software 87 | distributed under the License is distributed on an "AS IS" BASIS, 88 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 89 | See the License for the specific language governing permissions and 90 | limitations under the License. -------------------------------------------------------------------------------- /bascomtask-core/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | 7 | com.ebay.bascomtask 8 | bascomtask-project 9 | 2.1.1-SNAPSHOT 10 | 11 | 12 | bascomtask-core 13 | jar 14 | 15 | 16 | 17 | ch.qos.logback 18 | logback-classic 19 | 1.2.13 20 | 21 | 22 | 23 | junit 24 | junit 25 | 4.13.1 26 | test 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/annotations/Light.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.annotations; 18 | 19 | import java.lang.annotation.Documented; 20 | import java.lang.annotation.Inherited; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | import static java.lang.annotation.ElementType.METHOD; 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | /** 28 | * Marks a task method that is fast, likely because it does not make external 29 | * calls or access a database. A light method is executed without creating an 30 | * additional thread. 31 | * 32 | * @author Brendan McCarthy 33 | */ 34 | @Documented 35 | @Retention(RUNTIME) 36 | @Target(METHOD) 37 | @Inherited 38 | public @interface Light { 39 | } 40 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/ActiveManager.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 | class ActiveManager { 20 | private static ThreadLocal configured = new ThreadLocal<>(); 21 | 22 | static Orchestrator current() { 23 | return configured.get(); 24 | } 25 | 26 | static void set(Orchestrator orchestrator) { 27 | configured.set(orchestrator); 28 | } 29 | 30 | static void clear() { 31 | configured.remove(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/BascomTaskFuture.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.List; 21 | import java.util.concurrent.CompletableFuture; 22 | import java.util.concurrent.CompletionStage; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.Executor; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.function.*; 27 | 28 | /** 29 | * A specialization of CompletableFuture that tracks downstream Bindings for task methods that take this object 30 | * as an input, and also that ensures activation for standard CompletableFuture access operations (with the exception 31 | * of Compose operations -- see below). A call to {@link Orchestrator#task(TaskInterface)} returns an instance of this 32 | * class. When the user task returns its CompleteFuture result, that is bound to this class so that when the user CF 33 | * is completed, the BascomTask thread-optimization invocation logic can be applied. If the Bindings simply listened 34 | * to the CF directly then we'd have to spawn a thread for each one always. 35 | *
 36 |  *
 37 |  *     CF returned by user task or supplied externally
 38 |  *                       |
 39 |  *                 BascomTaskFuture
 40 |  *                    /    \
 41 |  *                   /      \
 42 |  *              Binding1   Binding2
 43 |  *                  |        |
 44 |  *             userTask1   userTask2
 45 |  * 
46 | * 47 | * @author Brendan McCarthy 48 | */ 49 | class BascomTaskFuture extends CompletableFuture { 50 | 51 | // The Binding for which this object holds the output 52 | private final Binding binding; 53 | 54 | // Holds activated bindings that are waiting on this CF (actually the CF target of this CF) to be completed, 55 | // set to null once that completion happens 56 | private List> listenerBindings = new ArrayList<>(); 57 | 58 | // Serves to avoid thread conflicts when adding to and readingFrom listenerBindings 59 | private final Object listenerLock = new Object(); 60 | 61 | BascomTaskFuture(Binding binding) { 62 | this.binding = binding; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "BascomTaskFuture[" + getBinding() + "]==>" + super.toString(); 68 | } 69 | 70 | Binding getBinding() { 71 | return binding; 72 | } 73 | 74 | /** 75 | * Registers this object as a listener on the provided CF which might or might not be another BascomTaskFuture. 76 | * 77 | * @param cf to register on 78 | */ 79 | void bind(CompletableFuture cf) { 80 | cf.thenAccept(this::finish).whenComplete((msg, ex) -> { 81 | if (ex != null) { 82 | Throwable cause = ex.getCause(); // Unwrap from java.util.concurrent.CompletionException 83 | binding.faultForward(cause); 84 | } 85 | }); 86 | } 87 | 88 | void propagateException(Throwable t, List fates) { 89 | for (Binding nextBinding : this.listenerBindings) { 90 | BascomTaskFuture bascomTaskFuture = nextBinding.getOutput(); 91 | bascomTaskFuture.binding.faultForward(t, fates); 92 | } 93 | } 94 | 95 | private void finish(T t) { 96 | complete(t); 97 | List> lbs; 98 | synchronized (listenerLock) { 99 | lbs = listenerBindings; 100 | // Setting to null is an indication that should any other Binding be activated that depends 101 | // on us, they should process our already-complete result directly 102 | listenerBindings = null; 103 | } 104 | binding.onCompletion(lbs); 105 | } 106 | 107 | Binding activate(Binding becomingActivated, Binding pending, TimeBox timeBox) { 108 | boolean complete = true; 109 | if (listenerBindings != null) { 110 | // If this CF has already completed, listenerBindings will be null 111 | synchronized (listenerLock) { 112 | if (listenerBindings != null) { 113 | // Set listeners before activating, in case execution occurs 114 | complete = false; 115 | listenerBindings.add(becomingActivated); 116 | } 117 | } 118 | } 119 | 120 | pending = binding.activate(pending, timeBox); 121 | if (complete) { 122 | // Only propagate forward if not done already 123 | pending = becomingActivated.argReady(pending); 124 | } 125 | return pending; 126 | } 127 | 128 | private static RuntimeException rethrow(ExecutionException e) { 129 | Throwable t = e.getCause(); 130 | if (t instanceof RuntimeException) { 131 | return (RuntimeException) t; 132 | } 133 | return new RuntimeException(t); 134 | } 135 | 136 | 137 | ////////////////////////////////////////////////////////////////////// 138 | // CompletableFuture overrides, each calls super after activating this 139 | ////////////////////////////////////////////////////////////////////// 140 | 141 | @Override 142 | public T get() throws InterruptedException { 143 | binding.engine.executeAndReuseUntilReady(this); 144 | try { 145 | return super.get(); 146 | } catch (ExecutionException e) { 147 | throw rethrow(e); 148 | } 149 | } 150 | 151 | @Override 152 | public T get(long timeout, TimeUnit unit) throws InterruptedException { 153 | binding.engine.executeAndReuseUntilReady(this, timeout, unit); 154 | try { 155 | return super.get(); 156 | } catch (ExecutionException e) { 157 | throw rethrow(e); 158 | } 159 | } 160 | 161 | @Override 162 | public T join() { 163 | binding.engine.executeAndReuseUntilReady(this); 164 | return super.join(); 165 | } 166 | 167 | @Override 168 | public T getNow(T valueIfAbsent) { 169 | binding.engine.executeAndReuseUntilReady(this); 170 | return super.getNow(valueIfAbsent); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/BaseFnTask.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 | * Base class for function tasks. 21 | * 22 | * @param of function, which may be Void 23 | * @param Leaf class 24 | * @author bremccarthy 25 | */ 26 | abstract class BaseFnTask> extends Binding /*implements SupplierTask*/ { 27 | private String name = null; 28 | 29 | public BaseFnTask(Engine engine) { 30 | super(engine); 31 | } 32 | 33 | /*@Override*/ 34 | public THIS name(String name) { 35 | this.name = name; 36 | return (THIS)this; 37 | } 38 | 39 | @Override 40 | String doGetExecutionName() { 41 | if (name == null) { 42 | return "FunctionTask"; 43 | } else { 44 | return name; 45 | } 46 | } 47 | 48 | @Override 49 | public TaskInterface getTask() { 50 | return null; 51 | } 52 | 53 | @Override 54 | public void formatActualSignature(StringBuilder sb) { 55 | 56 | } 57 | 58 | @Override 59 | protected Object invokeTaskMethod() { 60 | throw new RuntimeException("Invalid internal state for function task " + this); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/BinaryConditionalTask.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.Optional; 20 | import java.util.concurrent.CompletableFuture; 21 | 22 | /** 23 | * If-then conditional logic. 24 | * 25 | * @author Brendan McCarthy 26 | */ 27 | class BinaryConditionalTask extends ConditionalTask> implements TaskInterface>> { 28 | final BascomTaskFuture thenFuture; 29 | final BascomTaskFuture> thenOptional; 30 | final boolean thenActivate; 31 | 32 | BinaryConditionalTask(Engine engine, 33 | CompletableFuture condition, 34 | CompletableFuture thenValue, boolean thenActivate) { 35 | super(engine,condition); 36 | this.thenFuture = ensureWrapped(thenValue, false); 37 | this.thenOptional = new BascomTaskFuture<>(this); 38 | this.thenActivate = thenActivate; 39 | } 40 | 41 | @Override 42 | public TaskInterface getTask() { 43 | return this; 44 | } 45 | 46 | /** 47 | * Always activate the condition, and also active thenFuture if requested. 48 | * 49 | * @param pending to be processed 50 | * @return pending 51 | */ 52 | @Override 53 | Binding doActivate(Binding pending, TimeBox timeBox) { 54 | pending = condition.activate(this, pending, timeBox); 55 | pending = activateIf(pending, thenFuture, thenActivate, timeBox); 56 | return pending; 57 | } 58 | 59 | @Override 60 | protected Object invokeTaskMethod() { 61 | if (get(condition)) { 62 | ensureActivated(thenFuture); 63 | return thenFuture.thenApply(Optional::of); 64 | } else { 65 | return CompletableFuture.completedFuture(Optional.empty()); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/CommonConfig.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.ExecutorService; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | /** 23 | * Configuration settings. {@link GlobalOrchestratorConfig} and {@link Orchestrator} both implement this class, allowing 24 | * these values to be set at either level. On its creation, an Orchestrator's values are set from the current 25 | * global settings. 26 | * 27 | * @author Brendan McCarthy 28 | */ 29 | public interface CommonConfig { 30 | 31 | /** 32 | * Restore default settings. Applied on GlobalOrchestratorConfig, sets default values. Applied on Orchestrator, 33 | * sets values from current GlobalOrchestratorConfig. 34 | * 35 | * @param arg passed from user code 36 | */ 37 | void restoreConfigurationDefaults(Object arg); 38 | 39 | /** 40 | * Gets the most recently set mode. The global default is {@link SpawnMode#WHEN_NEEDED}. The Orchestrator 41 | * default is null, which results in using the global setting. 42 | * 43 | * @return most recently-set mode (possibly null) or default if none set 44 | */ 45 | SpawnMode getSpawnMode(); 46 | 47 | /** 48 | * Changes the thread-spawning logic to be consistent with the specified mode. If not set at a more 49 | * specific level (Orchestrator), the globally-set value is used. 50 | * 51 | * @param mode to set 52 | */ 53 | void setSpawnMode(SpawnMode mode); 54 | 55 | /** 56 | * Gets the current timeout. The default is zero, meaning no timeout is in effect. 57 | * 58 | * @return current timeout 59 | */ 60 | long getTimeoutMs(); 61 | 62 | /** 63 | * Sets the current timeout. When set to a value greater than zero, the behavior defined by the current 64 | * {@link #getTimeoutStrategy()} will apply. 65 | * 66 | * @param ms duration in milliseconds, zero means no timeout will be applied 67 | */ 68 | void setTimeoutMs(long ms); 69 | 70 | /** 71 | * Calls {@link #setTimeoutMs(long)} after calculating the millisecond duration from the supplied arguments. 72 | * 73 | * @param duration to timeout after 74 | * @param timeUnit to apply 75 | */ 76 | default void setTimeout(long duration, TimeUnit timeUnit) { 77 | setTimeoutMs(timeUnit.toMillis(duration)); 78 | } 79 | 80 | /** 81 | * Gets the current timeout strategy, default is {@link TimeoutStrategy#PREVENT_NEW}. 82 | * 83 | * @return default or strategy last set by {@link #setTimeoutStrategy(TimeoutStrategy)} 84 | */ 85 | TimeoutStrategy getTimeoutStrategy(); 86 | 87 | /** 88 | * Sets the strategy to apply if a timeout greater than zero is in effect and is exceeded. 89 | * 90 | * @param strategy to set 91 | */ 92 | void setTimeoutStrategy(TimeoutStrategy strategy); 93 | 94 | ExecutorService getExecutorService(); 95 | 96 | /** 97 | * Resets the service used by this framework for spawning threads. Global default is 98 | * Executors.newFixedThreadPool({@link GlobalOrchestratorConfig#DEFAULT_FIXED_THREADPOOL_SIZE}. 99 | * 100 | * @param executorService to set as new default 101 | */ 102 | void setExecutorService(ExecutorService executorService); 103 | 104 | void restoreDefaultExecutorService(); 105 | 106 | /** 107 | * Adds a TaskRunner that will be processed before any existing TaskRunner. 108 | * 109 | * @param taskRunner to add 110 | */ 111 | void firstInterceptWith(TaskRunner taskRunner); 112 | 113 | /** 114 | * Adds a TaskRunner that will be processed after any existing TaskRunner. 115 | * 116 | * @param taskRunner to add 117 | */ 118 | void lastInterceptWith(TaskRunner taskRunner); 119 | 120 | /** 121 | * Returns the current number of interceptors, as set by {@link #firstInterceptWith(TaskRunner)}, 122 | * {@link #lastInterceptWith(TaskRunner)}, or for Orchestrators as set from current global settings. 123 | * 124 | * @return interceptor count 125 | */ 126 | int getNumberOfInterceptors(); 127 | 128 | /** 129 | * Removes the given TaskRunner from the current list, if it is there. It will exit silently if not there. 130 | * 131 | * @param taskRunner to remove 132 | */ 133 | void removeInterceptor(TaskRunner taskRunner); 134 | 135 | /** 136 | * Removes all interceptors from the current list. 137 | */ 138 | void removeAllInterceptors(); 139 | } 140 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/ConditionalTask.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 | * Base class for conditional logic (if-then or if-then-else) optimized for thread execution. The task also 23 | * serves as its own {@link Binding} to reduce object creation for this built-in case. 24 | * 25 | * @author Brendan McCarthy 26 | */ 27 | abstract class ConditionalTask extends Binding { 28 | protected final BascomTaskFuture condition; 29 | 30 | ConditionalTask(Engine engine, CompletableFuture condition) { 31 | super(engine); 32 | this.condition = ensureWrapped(condition, true); 33 | } 34 | 35 | protected Binding activateIf(Binding pending, BascomTaskFuture bascomTaskFuture, boolean activate, TimeBox timeBox) { 36 | if (activate && bascomTaskFuture != null) { 37 | // Activate it, but don't connect its completion yet -- that will happen once condition is resolved 38 | pending = bascomTaskFuture.getBinding().activate(pending, timeBox); 39 | } 40 | return pending; 41 | } 42 | 43 | /** 44 | * Ensure that the future is activated if it is not already. 45 | * @param bf to ensure to activate 46 | */ 47 | protected void ensureActivated(BascomTaskFuture bf) { 48 | engine.executeAndReuseUntilReady(bf); 49 | } 50 | 51 | @Override 52 | String doGetExecutionName() { 53 | return "<>"; 54 | } 55 | 56 | @Override 57 | public void formatActualSignature(StringBuilder sb) { 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/ConsumerTask.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.*; 23 | 24 | /** 25 | * Function tasks added through 'fn()' methods on {@link Orchestrator} allow CompletableFuture tasks to 26 | * be created from lambda expressions that do not return a value. 27 | * 28 | * @author Brendan McCarthy 29 | */ 30 | public interface ConsumerTask extends TaskInterface { 31 | /** 32 | * Creates a CompletableFuture around the lambda expression. 33 | * 34 | * @return evaluated CompletableFuture 35 | */ 36 | CompletableFuture apply(); 37 | 38 | /** 39 | * An ConsumerTask that takes 1 argument. 40 | * 41 | * @param type of input 42 | */ 43 | class ConsumerTask1 extends BaseFnTask> implements ConsumerTask { 44 | 45 | private final Consumer fn; 46 | private final BascomTaskFuture input; 47 | 48 | public ConsumerTask1(Engine engine, CompletableFuture input, Consumer fn) { 49 | super(engine); 50 | this.fn = fn; 51 | this.input = ensureWrapped(input,true); 52 | } 53 | 54 | @Override 55 | Binding doActivate(Binding pending, TimeBox timeBox) { 56 | return input.activate(this, pending, timeBox); 57 | } 58 | 59 | @Override 60 | @Light 61 | public CompletableFuture apply() { 62 | IN value = get(input); 63 | fn.accept(value); 64 | return complete(); 65 | } 66 | } 67 | 68 | 69 | /** 70 | * An ConsumerTask that takes 2 arguments. 71 | * 72 | * @param type of first input 73 | * @param type of second input 74 | */ 75 | class ConsumerTask2 extends BaseFnTask> implements ConsumerTask { 76 | private final BiConsumer fn; 77 | private final BascomTaskFuture cf1; 78 | private final BascomTaskFuture cf2; 79 | 80 | public ConsumerTask2(Engine engine, CompletableFuture cf1, CompletableFuture cf2, BiConsumer fn) { 81 | super(engine); 82 | this.cf1 = ensureWrapped(cf1,true); 83 | this.cf2 = ensureWrapped(cf2,true); 84 | this.fn = fn; 85 | } 86 | 87 | @Override 88 | @Light 89 | public CompletableFuture apply() { 90 | IN1 v1 = get(cf1); 91 | IN2 v2 = get(cf2); 92 | fn.accept(v1, v2); 93 | return complete(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/ExternalBinding.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 | * Binding for a CompletableFuture not managed by the framework, so there is no actual user task. 23 | * 24 | * @author Brendan McCarthy 25 | */ 26 | class ExternalBinding extends Binding { 27 | 28 | ExternalBinding(Engine engine, CompletableFuture cf) { 29 | super(engine, cf); 30 | } 31 | 32 | @Override 33 | Binding doActivate(Binding pending, TimeBox timeBox) { 34 | return pending; 35 | } 36 | 37 | @Override 38 | protected Object invokeTaskMethod() { 39 | throw new RuntimeException("Not expected"); 40 | } 41 | 42 | @Override 43 | String doGetExecutionName() { 44 | return "<>"; 45 | } 46 | 47 | @Override 48 | public TaskInterface getTask() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public void formatActualSignature(StringBuilder sb) { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/FateTask.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.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.util.List; 23 | import java.util.concurrent.CompletableFuture; 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | 26 | /** 27 | * Implements {@link Orchestrator#fate(CompletableFuture[])}. 28 | * 29 | * @author Brendan McCarthy 30 | */ 31 | class FateTask extends Binding implements TaskInterface { 32 | private static final Logger LOG = LoggerFactory.getLogger(FateTask.class); 33 | 34 | private CompletableFuture result = CompletableFuture.completedFuture(false); 35 | private final AtomicBoolean executed = new AtomicBoolean(false); 36 | 37 | FateTask(Engine engine, CompletableFuture... cfs) { 38 | super(engine); 39 | for (CompletableFuture cf : cfs) { 40 | ensureWrapped(cf, true); 41 | } 42 | } 43 | 44 | @Override 45 | protected Object invokeTaskMethod() { 46 | return result; 47 | } 48 | 49 | /** 50 | * Intercepts parent method to prevent fault propagation. 51 | * 52 | * @param t being thrown 53 | * @param fates list of FateTasks to collect 54 | */ 55 | @Override 56 | void faultForward(Throwable t, List fates) { 57 | if (executed.compareAndSet(false, true)) { 58 | result = CompletableFuture.completedFuture(true); 59 | LOG.debug("Swallowing forward-fault"); 60 | fates.add(this); 61 | } 62 | } 63 | 64 | @Override 65 | String doGetExecutionName() { 66 | return "<>"; 67 | } 68 | 69 | @Override 70 | public TaskInterface getTask() { 71 | return this; 72 | } 73 | 74 | @Override 75 | public void formatActualSignature(StringBuilder sb) { 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /bascomtask-core/src/main/java/com/ebay/bascomtask/core/GlobalOrchestratorConfig.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.List; 21 | import java.util.concurrent.ExecutorService; 22 | import java.util.concurrent.Executors; 23 | import java.util.function.BiConsumer; 24 | import java.util.function.Supplier; 25 | 26 | /** 27 | * Maintains configuration settings that will be applied to new Orchestrators. 28 | * 29 | * @author Brendan McCarthy 30 | */ 31 | public class GlobalOrchestratorConfig { 32 | public final static int DEFAULT_FIXED_THREADPOOL_SIZE = 20; 33 | private static final ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newFixedThreadPool(DEFAULT_FIXED_THREADPOOL_SIZE); 34 | 35 | private static final Config DEFUALT_CONFIG = new Config() { 36 | public void afterDefaultInitialization(Orchestrator orchestrator, Object arg) { 37 | } 38 | }; 39 | 40 | private static Config globalConfig = DEFUALT_CONFIG; 41 | 42 | interface ExtendedConfig extends CommonConfig { 43 | void initializeWith(BiConsumer fn); 44 | } 45 | 46 | /** 47 | * Default implementation maintains values that are transferred to an Orchestrator on demand. 48 | */ 49 | public abstract static class Config implements ExtendedConfig { 50 | protected ExecutorService executorService; 51 | protected final List first = new ArrayList<>(); 52 | protected final List last = new ArrayList<>(); 53 | protected SpawnMode spawnMode; 54 | protected long timeoutMs; 55 | protected TimeoutStrategy timeoutStrategy = TimeoutStrategy.PREVENT_NEW; 56 | protected final List> initializers = new ArrayList<>(); 57 | 58 | protected Config() { 59 | restoreConfigurationDefaults(null); 60 | } 61 | 62 | /** 63 | * Transfers configuration settings to the supplied orchestrator. 64 | * 65 | * @param orchestrator to update 66 | * @param arg passed from user code, see {@link #afterDefaultInitialization(Orchestrator, Object)} 67 | */ 68 | final public void updateConfigurationOn(Orchestrator orchestrator, Object arg) { 69 | orchestrator.setSpawnMode(getSpawnMode()); 70 | orchestrator.setTimeoutMs(getTimeoutMs()); 71 | orchestrator.setTimeoutStrategy(getTimeoutStrategy()); 72 | orchestrator.setExecutorService(getExecutorService()); 73 | for (TaskRunner next : first) { 74 | orchestrator.firstInterceptWith(next); 75 | } 76 | for (TaskRunner next : last) { 77 | orchestrator.lastInterceptWith(next); 78 | } 79 | for (BiConsumer next : initializers) { 80 | next.accept(orchestrator, arg); 81 | } 82 | afterDefaultInitialization(orchestrator, arg); 83 | } 84 | 85 | /** 86 | * Subclasses can override to provide custom logic for {@link #updateConfigurationOn(Orchestrator, Object)}. 87 | * The arg parameter is what is passed to {@link Orchestrator#create(String, Object)}, without modification. 88 | * It is intended to allow for this method to know to apply different settings for different orchestrators, 89 | * if desired. 90 | * 91 | * @param orchestrator to update 92 | * @param arg passed from user code 93 | */ 94 | abstract public void afterDefaultInitialization(Orchestrator orchestrator, Object arg); 95 | 96 | @Override 97 | public final void restoreConfigurationDefaults(Object arg) { 98 | globalConfig = DEFUALT_CONFIG; 99 | setSpawnMode(SpawnMode.WHEN_NEEDED); 100 | setTimeoutMs(0); 101 | setTimeoutStrategy(TimeoutStrategy.PREVENT_NEW); 102 | removeAllInterceptors(); 103 | restoreDefaultExecutorService(); 104 | initializers.clear(); 105 | } 106 | 107 | @Override 108 | public SpawnMode getSpawnMode() { 109 | return spawnMode; 110 | } 111 | 112 | @Override 113 | public void setSpawnMode(SpawnMode mode) { 114 | this.spawnMode = mode; 115 | } 116 | 117 | @Override 118 | public long getTimeoutMs() { 119 | return timeoutMs; 120 | } 121 | 122 | @Override 123 | public void setTimeoutMs(long ms) { 124 | this.timeoutMs = ms; 125 | } 126 | 127 | @Override 128 | public TimeoutStrategy getTimeoutStrategy() { 129 | return timeoutStrategy; 130 | } 131 | 132 | @Override 133 | public void setTimeoutStrategy(TimeoutStrategy strategy) { 134 | this.timeoutStrategy = strategy; 135 | } 136 | 137 | @Override 138 | public ExecutorService getExecutorService() { 139 | return executorService; 140 | } 141 | 142 | @Override 143 | public void setExecutorService(ExecutorService executorService) { 144 | this.executorService = executorService; 145 | } 146 | 147 | @Override 148 | public void restoreDefaultExecutorService() { 149 | this.executorService = DEFAULT_EXECUTOR_SERVICE; 150 | } 151 | 152 | @Override 153 | public void firstInterceptWith(TaskRunner runner) { 154 | first.add(runner); 155 | } 156 | 157 | @Override 158 | public void lastInterceptWith(TaskRunner runner) { 159 | last.add(runner); 160 | } 161 | 162 | @Override 163 | public int getNumberOfInterceptors() { 164 | return first.size() + last.size(); 165 | } 166 | 167 | @Override 168 | public void removeInterceptor(TaskRunner taskRunner) { 169 | first.remove(taskRunner); 170 | last.remove(taskRunner); 171 | } 172 | 173 | @Override 174 | public void removeAllInterceptors() { 175 | first.clear(); 176 | last.clear(); 177 | } 178 | 179 | @Override 180 | public void initializeWith(BiConsumer 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 task = $.vfn( 195 | t1, 196 | t2, 197 | (x, y) -> simple.input = x + y); 198 | 199 | task.get(); 200 | assertEquals(5, simple.input); 201 | } 202 | 203 | @Test 204 | public void supSupBiConsumer() throws Exception { 205 | Simple simple = new Simple(); 206 | CompletableFuture task = $.vfn( 207 | () -> 2, 208 | () -> 3, 209 | (x, y) -> simple.input = x + y); 210 | 211 | task.get(); 212 | assertEquals(5, simple.input); 213 | } 214 | 215 | 216 | @Test 217 | public void taskAdaptor() throws Exception { 218 | int v = 5; 219 | CompletableFuture cf = $.task(new Simple(), s -> s.negate(v)); 220 | int got = cf.get(); 221 | assertEquals(-v, got); 222 | } 223 | 224 | @Test 225 | public void taskAdaptorGet() throws Exception { 226 | int v = 5; 227 | CompletableFuture cf = $.task(new Simple(), s -> s.negate(v)); 228 | int got = cf.get(); 229 | assertEquals(-v, got); 230 | } 231 | 232 | @Test 233 | public void taskAdaptorVoid() throws Exception { 234 | int v = 5; 235 | Simple simple = new Simple(); 236 | CompletableFuture cf = $.voidTask(simple, s -> s.consume(v)); 237 | assertEquals(0, simple.input); 238 | cf.get(); 239 | assertEquals(v, simple.input); 240 | } 241 | 242 | @Test 243 | public void fnExecuteAndWait() { 244 | CompletableFuture cf1 = $.task(task()).ret(1); 245 | CompletableFuture cf2 = $.fn(cf1,x->x+1); 246 | //System.out.println(cf2.join()); 247 | $.activateAndWait(cf1,cf2); 248 | System.out.println("E&W"); 249 | assertEquals(Integer.valueOf(2),cf2.join()); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/IThreadTask.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 | 23 | /** 24 | * A task with methods that are with/without a Light records whether its methods are executed, 25 | * and whose methods return the thread that executes them. 26 | * 27 | * @author Brendan McCarthy 28 | */ 29 | interface IThreadTask extends TaskInterface { 30 | CompletableFuture computeAnnotated(); 31 | 32 | CompletableFuture computeNotAnnotated(); 33 | 34 | class ThreadTask implements IThreadTask { 35 | private boolean defaultLight = false; 36 | private boolean defaultRunSpawned = false; 37 | boolean calledAnnotated = false; 38 | boolean calledNotAnnotated = false; 39 | 40 | @Override 41 | public ThreadTask light() { 42 | defaultLight = true; 43 | return this; 44 | } 45 | 46 | @Override 47 | public boolean isLight() { 48 | return defaultLight; 49 | } 50 | 51 | @Override 52 | public ThreadTask runSpawned() { 53 | defaultRunSpawned = true; 54 | return this; 55 | } 56 | 57 | @Override 58 | public boolean isRunSpawned() { 59 | return defaultRunSpawned; 60 | } 61 | 62 | @Override 63 | @Light 64 | public CompletableFuture computeAnnotated() { 65 | calledAnnotated = true; 66 | return complete(Thread.currentThread()); 67 | } 68 | 69 | @Override 70 | public CompletableFuture computeNotAnnotated() { 71 | calledNotAnnotated = true; 72 | return complete(Thread.currentThread()); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/NamingTask.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 | * Task with naming subclasses employing different naming strategies. 21 | * 22 | * @author Brendan Mccarthy 23 | */ 24 | interface NamingTask extends TaskInterface { 25 | 26 | class OverridesNothing implements NamingTask { 27 | 28 | } 29 | 30 | class OverridesGet implements NamingTask { 31 | @Override 32 | public String getName() { 33 | return "any_name_will_do"; 34 | } 35 | } 36 | 37 | class OverridesGetAndSet implements NamingTask { 38 | private String name; 39 | 40 | @Override 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | @Override 46 | public NamingTask name(String name) { 47 | this.name = name; 48 | return this; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/OrchestratorPassingTest.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 org.junit.Assert.assertEquals; 24 | 25 | /** 26 | * Passing Orchestrators to nested tasks. 27 | * 28 | * @author Brendan McCarthy 29 | */ 30 | public class OrchestratorPassingTest extends BaseOrchestratorTest { 31 | 32 | private static int FIXED_VALUE = 5; 33 | 34 | interface IFooTask extends TaskInterface { 35 | CompletableFuture simple(); 36 | CompletableFuture external(); 37 | CompletableFuture> nestSimple(); 38 | CompletableFuture> nestExternal(); 39 | } 40 | 41 | static class FooTask implements IFooTask { 42 | 43 | @Override 44 | public CompletableFuture simple() { 45 | return complete(FIXED_VALUE); 46 | } 47 | @Override 48 | public CompletableFuture> nestSimple() { 49 | Orchestrator nested = Orchestrator.current(); 50 | CompletableFuture sv = nested.task(new FooTask()).simple(); 51 | return complete(sv); 52 | } 53 | @Override 54 | public CompletableFuture external() { 55 | return CompletableFuture.supplyAsync(()->{ 56 | try { 57 | Thread.sleep(30); 58 | } catch (InterruptedException e) { 59 | e.printStackTrace(); 60 | } 61 | return FIXED_VALUE; 62 | }); 63 | } 64 | @Override 65 | public CompletableFuture> nestExternal() { 66 | Orchestrator nested = Orchestrator.current(); 67 | CompletableFuture sv = nested.task(new FooTask()).simple(); 68 | return complete(sv); 69 | } 70 | } 71 | 72 | @Test 73 | public void nested() throws Exception { 74 | Orchestrator $ = Orchestrator.create(); 75 | CompletableFuture> got = $.task(new FooTask()).nestSimple(); 76 | CompletableFuture c1 = got.get(); 77 | Integer v = c1.get(); 78 | assertEquals(FIXED_VALUE,(int)v); 79 | } 80 | 81 | @Test 82 | public void external() throws Exception { 83 | Orchestrator $ = Orchestrator.create(); 84 | CompletableFuture> got = $.task(new FooTask()).nestExternal(); 85 | CompletableFuture c1 = got.get(); 86 | Integer v = c1.get(); 87 | assertEquals(FIXED_VALUE,(int)v); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/TaskInterruptedException.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 | * Sample response to being interrupted on timeout. 21 | * 22 | * @author Brendan Mccarthy 23 | */ 24 | public class TaskInterruptedException extends RuntimeException { 25 | public TaskInterruptedException(String msg) { 26 | super(msg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/TaskVariations.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 | * Variations combinations of task hierarchies. 23 | * 24 | * @author Brendan Mccarthy 25 | */ 26 | public class TaskVariations { 27 | static class Base { 28 | public CompletableFuture ret(String s) { 29 | return CompletableFuture.completedFuture(s); 30 | } 31 | } 32 | 33 | interface NoTask { 34 | 35 | } 36 | 37 | static class No implements NoTask { 38 | } 39 | 40 | interface YesTask extends TaskInterface { 41 | CompletableFuture ret(String s); 42 | } 43 | 44 | static class Yes extends Base implements YesTask { 45 | 46 | } 47 | 48 | interface SubYesTask extends YesTask { 49 | } 50 | 51 | static class SubYes extends Base implements SubYesTask { 52 | } 53 | 54 | interface YesTaskP extends TaskInterface> { 55 | CompletableFuture returnType(TYPE v); 56 | } 57 | 58 | static class YesP extends Base implements YesTaskP { 59 | public CompletableFuture returnType(TYPE v) { 60 | return CompletableFuture.completedFuture(v); 61 | } 62 | } 63 | 64 | interface YesTaskBefore extends NoTask, TaskInterface { 65 | CompletableFuture ret(String s); 66 | } 67 | 68 | static class YesBefore extends Base implements YesTaskBefore { 69 | } 70 | 71 | interface YesTaskAfter extends TaskInterface, NoTask { 72 | CompletableFuture ret(String s); 73 | } 74 | 75 | static class YesAfter extends Base implements YesTaskAfter { 76 | } 77 | 78 | interface YesAndNoTask extends NoTask, YesTask { 79 | 80 | } 81 | 82 | static class YesAndNo extends Base implements YesAndNoTask { 83 | } 84 | 85 | static class YesAndNoClass extends Base implements NoTask, YesTask { 86 | } 87 | 88 | static class YesAndNoSubClass extends YesAndNoClass { 89 | } 90 | 91 | static class NoClassYes extends No implements YesTask { 92 | @Override 93 | public CompletableFuture ret(String s) { 94 | return CompletableFuture.completedFuture(s); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/TaskVariationsTest.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 | * Core BascomTask execution tests. These should be runnable before anything test files. 28 | * 29 | * @author Brendan McCarthy 30 | */ 31 | public class TaskVariationsTest extends BaseOrchestratorTest { 32 | 33 | private final static String SV = "xyz"; 34 | 35 | @Test 36 | public void yes() throws Exception { 37 | CompletableFuture s = $.task(new Yes()).ret(SV); 38 | assertEquals(SV, s.get()); 39 | } 40 | 41 | @Test 42 | public void yesp() throws Exception { 43 | CompletableFuture s = $.task(new YesP()).returnType(SV); 44 | assertEquals(SV, s.get()); 45 | } 46 | 47 | @Test 48 | public void subYes() throws Exception { 49 | CompletableFuture s = $.task(new SubYes()).ret(SV); 50 | assertEquals(SV, s.get()); 51 | } 52 | 53 | @Test 54 | public void yesBefore() throws Exception { 55 | CompletableFuture s = $.task(new YesBefore()).ret(SV); 56 | assertEquals(SV, s.get()); 57 | } 58 | 59 | @Test 60 | public void yesAfter() throws Exception { 61 | CompletableFuture s = $.task(new YesAfter()).ret(SV); 62 | assertEquals(SV, s.get()); 63 | } 64 | 65 | @Test 66 | public void yesAndNo() throws Exception { 67 | CompletableFuture s = $.task(new YesAndNo()).ret(SV); 68 | assertEquals(SV, s.get()); 69 | } 70 | 71 | @Test 72 | public void yesAndNoClass() throws Exception { 73 | CompletableFuture s = $.task(new YesAndNoClass()).ret(SV); 74 | assertEquals(SV, s.get()); 75 | } 76 | 77 | @Test 78 | public void yesAndNoSubClass() throws Exception { 79 | CompletableFuture s = $.task(new YesAndNoSubClass()).ret(SV); 80 | assertEquals(SV, s.get()); 81 | } 82 | 83 | @Test 84 | public void noClassYes() throws Exception { 85 | CompletableFuture s = $.task(new NoClassYes()).ret(SV); 86 | assertEquals(SV, s.get()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/core/UtilsTest.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 | import org.junit.Test; 21 | 22 | import java.lang.reflect.Method; 23 | import java.util.concurrent.CompletableFuture; 24 | 25 | import static org.junit.Assert.*; 26 | 27 | /** 28 | * Utilities test. 29 | * 30 | * @author Brendan McCarthy 31 | */ 32 | public class UtilsTest extends BaseOrchestratorTest { 33 | 34 | @Test 35 | public void format() { 36 | final String base = "BASE"; 37 | StringBuilder sb = new StringBuilder(); 38 | Object[] args = { 39 | 3, 40 | "foo", 41 | CompletableFuture.completedFuture(7), 42 | CompletableFuture.supplyAsync(() -> sleepThen(40, 9)) 43 | }; 44 | Utils.formatFullSignature(sb, base, args); 45 | assertEquals(base + "(3,foo,+7,-)", sb.toString()); 46 | } 47 | 48 | static class Base { 49 | @Light 50 | public int bar() {return 1;} 51 | } 52 | 53 | static class Sub extends Base { 54 | public int bar() {return super.bar();} 55 | } 56 | 57 | @Test 58 | public void annotationPresent() throws Exception { 59 | Method method = Sub.class.getMethod("bar"); 60 | Sub sub = new Sub(); 61 | assertEquals(1,sub.bar()); // just to keep static analysis tools happy 62 | Light ann = Utils.getAnnotation(sub,method,Light.class); 63 | assertNotNull(ann); 64 | } 65 | 66 | @Test 67 | public void annotationNoPresent() throws Exception { 68 | Method method = Sub.class.getMethod("bar"); 69 | Sub sub = new Sub(); 70 | assertEquals(1,sub.bar()); // just to keep static analysis tools happy 71 | Override ann = Utils.getAnnotation(sub,method,Override.class); 72 | assertNull(ann); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/runners/LogTaskRunnerTest.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.junit.Test; 20 | 21 | /** 22 | * Tests LogTaskRunner. 23 | * 24 | * @author Brendan McCarthy 25 | */ 26 | public class LogTaskRunnerTest { 27 | 28 | private final static MockTaskRun R1 = new MockTaskRun("the", "task"); 29 | 30 | private void write(LogTaskRunner runner, LogTaskLevel level) { 31 | runner.setLevel(level); 32 | Object fromBefore = runner.before(R1); 33 | Thread thread = Thread.currentThread(); 34 | runner.executeTaskMethod(R1, thread, fromBefore); 35 | runner.onComplete(R1, fromBefore, false); 36 | runner.onComplete(R1, fromBefore, true); 37 | } 38 | 39 | private void runAll(boolean full) { 40 | LogTaskRunner runner = new LogTaskRunner(); 41 | runner.setFullSignatureLogging(full); 42 | for (LogTaskLevel next : LogTaskLevel.values()) { 43 | write(runner, next); 44 | } 45 | } 46 | 47 | @Test 48 | public void empty() { 49 | runAll(false); 50 | runAll(true); 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/runners/MockTaskRun.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.TaskInterface; 20 | import com.ebay.bascomtask.core.TaskRun; 21 | import com.ebay.bascomtask.core.TaskRunner; 22 | 23 | /** 24 | * Mocks TaskRun for testing TaskRunners. 25 | * 26 | * @author Brendan McCarthy 27 | */ 28 | public class MockTaskRun implements TaskRun { 29 | private final String name; 30 | private final String npm; 31 | private long startedAt; 32 | private long endedAt; 33 | private long completedAt; 34 | 35 | MockTaskRun(String name, String method) { 36 | this.name = name; 37 | this.npm = name + '.' + method; 38 | } 39 | 40 | void sim(TaskRunner taskRunner, long startedAt, long endedAt) { 41 | sim(taskRunner, startedAt, endedAt, endedAt); 42 | } 43 | 44 | void sim(TaskRunner taskRunner, long startedAt, long endedAt, long completedAt) { 45 | this.startedAt = startedAt; 46 | this.endedAt = endedAt; 47 | this.completedAt = completedAt; 48 | Object fromBefore = taskRunner.before(this); 49 | taskRunner.executeTaskMethod(this, Thread.currentThread(), fromBefore); 50 | boolean doneOnExit = endedAt <= startedAt; 51 | taskRunner.onComplete(this, fromBefore, doneOnExit); 52 | } 53 | 54 | @Override 55 | public TaskInterface getTask() { 56 | return null; 57 | } 58 | 59 | @Override 60 | public String getName() { 61 | return name; 62 | } 63 | 64 | @Override 65 | public String getTaskPlusMethodName() { 66 | return npm; 67 | } 68 | 69 | @Override 70 | public void formatActualSignature(StringBuilder sb) { 71 | sb.append(npm); 72 | sb.append("()"); 73 | } 74 | 75 | @Override 76 | public boolean isLight() { 77 | return false; 78 | } 79 | 80 | @Override 81 | public Object run() { 82 | return this; // doesn't matter 83 | } 84 | 85 | @Override 86 | public long getStartedAt() { 87 | return startedAt; 88 | } 89 | 90 | @Override 91 | public long getEndedAt() { 92 | return endedAt; 93 | } 94 | 95 | @Override 96 | public long getCompletedAt() { 97 | return completedAt; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/runners/ProfilingTaskRunnerTest.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.*; 20 | 21 | import static com.ebay.bascomtask.core.UberTask.*; 22 | import static org.junit.Assert.*; 23 | 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | 27 | /** 28 | * Tests ProfilingTaskRunner. 29 | * 30 | * @author Brendan McCarthy 31 | */ 32 | public class ProfilingTaskRunnerTest extends BaseOrchestratorTest { 33 | 34 | private static class FakeTaskRun implements TaskRun { 35 | 36 | private final long startedAt; 37 | private final long endedAt; 38 | private final long completedAt; 39 | private final String taskName; 40 | private final String methodName; 41 | 42 | FakeTaskRun(long startedAt, long endedAt, long completedAt, String taskName, String methodName) { 43 | this.startedAt = startedAt; 44 | this.endedAt = endedAt; 45 | this.completedAt = completedAt; 46 | this.taskName = taskName; 47 | this.methodName = methodName; 48 | } 49 | 50 | @Override 51 | public TaskInterface getTask() { 52 | return null; 53 | } 54 | 55 | @Override 56 | public String getName() { 57 | return taskName; 58 | } 59 | 60 | @Override 61 | public String getTaskPlusMethodName() { 62 | return taskName + '.' + methodName; 63 | } 64 | 65 | @Override 66 | public void formatActualSignature(StringBuilder sb) { 67 | 68 | } 69 | 70 | @Override 71 | public boolean isLight() { 72 | return false; 73 | } 74 | 75 | @Override 76 | public Object run() { 77 | return null; 78 | } 79 | 80 | @Override 81 | public long getStartedAt() { 82 | return startedAt; 83 | } 84 | 85 | @Override 86 | public long getEndedAt() { 87 | return endedAt; 88 | } 89 | 90 | @Override 91 | public long getCompletedAt() { 92 | return completedAt; 93 | } 94 | } 95 | 96 | private ProfilingTaskRunner taskRunner; 97 | 98 | @Before 99 | public void before() { 100 | super.before(); 101 | taskRunner = new ProfilingTaskRunner(); 102 | } 103 | 104 | private void run(String threadName, long startedAt, long endedAt, String name, String method) { 105 | run(threadName, startedAt, endedAt, endedAt, name, method); 106 | } 107 | 108 | private void run(String threadName, long startedAt, long endedAt, long completedAt, String name, String method) { 109 | String orgName = Thread.currentThread().getName(); 110 | Thread.currentThread().setName(threadName); 111 | Thread thread = Thread.currentThread(); 112 | FakeTaskRun fakeRun = new FakeTaskRun(startedAt, endedAt, completedAt, name, method); 113 | try { 114 | taskRunner.executeTaskMethod(fakeRun, thread, null); 115 | taskRunner.onComplete(fakeRun, null, true); 116 | } finally { 117 | Thread.currentThread().setName(orgName); 118 | } 119 | } 120 | 121 | private static void log(Object x) { 122 | System.out.println(x); 123 | } 124 | 125 | private static final String T1 = "BLUE"; 126 | private static final String T2 = "GREEN"; 127 | private static final String T3 = "RED"; 128 | 129 | @Test 130 | public void test1() { 131 | run(T1, 1, 10, "blue", "dog"); 132 | String fmt = taskRunner.format(); 133 | log(fmt); 134 | assertTrue(fmt.contains(" 0| blue.dog ---")); 135 | } 136 | 137 | @Test 138 | public void testTwoSeq() { 139 | run(T1, 0, 10, "blue", "dog"); 140 | run(T1, 10, 20, "green", "hornet"); 141 | String fmt = taskRunner.format(); 142 | log(fmt); 143 | String exp = " 0 \n"; 144 | exp += " 0| blue.dog --- \n"; 145 | exp += " 10| green.hornet =-= \n"; 146 | exp += " 20| --- \n"; 147 | assertEquals(exp, fmt); 148 | } 149 | 150 | @Test 151 | public void testTwoOverlap() { 152 | run(T1, 0, 10, "blue", "dog"); 153 | run(T2, 7, 17, "green", "hornet"); 154 | String fmt = taskRunner.format(); 155 | log(fmt); 156 | assertTrue(fmt.contains("7| green.hornet - ---")); 157 | assertTrue(fmt.contains("17| ---")); 158 | } 159 | 160 | @Test 161 | public void testSeqAndOverlap() { 162 | run(T1, 0, 10, "blue", "dog"); 163 | run(T2, 7, 17, "green", "hornet"); 164 | run(T1, 10, 17, "blue", "pony"); 165 | run(T2, 18, 21, "green", "bird"); 166 | String fmt = taskRunner.format(); 167 | log(fmt); 168 | assertTrue(fmt.contains(" 10| blue.pony =-= - ")); 169 | assertTrue(fmt.contains(" 18| green.bird ---")); 170 | } 171 | 172 | @Test 173 | public void testThree() { 174 | run(T1, 0, 10, "blue", "dog"); 175 | run(T2, 7, 17, "green", "hornet"); 176 | run(T1, 10, 17, "blue", "pony"); 177 | run(T2, 18, 21, "green", "bird"); 178 | run(T3, 3, 21, "red", "cat"); 179 | String fmt = taskRunner.format(); 180 | log(fmt); 181 | assertTrue(fmt.contains("7| green.hornet - - ---")); 182 | assertTrue(fmt.contains("18| green.bird - ---")); 183 | } 184 | 185 | @Test 186 | public void test1xc() { 187 | run(T1, 1, 10, 15, "blue", "dog"); 188 | String fmt = taskRunner.format(); 189 | log(fmt); 190 | assertTrue(fmt.contains(" 0| blue.dog --- -+-")); 191 | assertTrue(fmt.contains(" 9| --- + ")); 192 | } 193 | 194 | @Test 195 | public void test2xc() { 196 | run(T1, 0, 10, 15, "gold", "pond"); 197 | run(T1, 10, 20, 25, "gold", "fish"); 198 | String fmt = taskRunner.format(); 199 | log(fmt); 200 | assertTrue(fmt.contains("0| gold.fish =-= + -+-")); 201 | } 202 | 203 | @Test 204 | public void withEngine() throws Exception { 205 | 206 | ProfilingTaskRunner taskRunner = new ProfilingTaskRunner(); 207 | 208 | assertEquals(0, $.getNumberOfInterceptors()); 209 | 210 | $.firstInterceptWith(taskRunner); 211 | 212 | assertEquals(1, $.getNumberOfInterceptors()); 213 | 214 | $.task(task()).name("blue").ret(1).get(); 215 | 216 | String fmt = taskRunner.format(); 217 | assertTrue(fmt.contains("0| blue.ret ---")); 218 | } 219 | 220 | private final ThreadLocal threadLocal = new ThreadLocal<>(); 221 | 222 | @Test 223 | public void globalConfigReplace() throws Exception { 224 | GlobalOrchestratorConfig.setConfig(new GlobalOrchestratorConfig.Config() { 225 | @Override 226 | public void afterDefaultInitialization(Orchestrator orchestrator, Object arg) { 227 | ProfilingTaskRunner ptr = new ProfilingTaskRunner(); 228 | threadLocal.set(ptr); 229 | orchestrator.firstInterceptWith(ptr); 230 | } 231 | }); 232 | 233 | Orchestrator $ = Orchestrator.create(); 234 | assertEquals(1, $.getNumberOfInterceptors()); 235 | 236 | $.task(task()).name("single").ret(1).get(); 237 | 238 | ProfilingTaskRunner ptr = threadLocal.get(); 239 | String got = ptr.format(); 240 | assertTrue(got.contains("0| single.ret ---")); 241 | } 242 | 243 | private void expectInterceptors(int count, Orchestrator orchestrator, LaneRunner laneRunner) { 244 | assertEquals(1, orchestrator.getNumberOfInterceptors()); 245 | assertEquals(count, laneRunner.runners.size()); 246 | } 247 | 248 | @Test 249 | public void installNone() throws Exception { 250 | try (LaneRunner laneRunner = GlobalOrchestratorConfig.interceptFirstOnCreate(ProfilingTaskRunner::new)) { 251 | assertEquals(0, laneRunner.runners.size()); 252 | } 253 | } 254 | 255 | @Test 256 | public void installSingle() throws Exception { 257 | try (LaneRunner laneRunner = GlobalOrchestratorConfig.interceptFirstOnCreate(ProfilingTaskRunner::new)) { 258 | Orchestrator $ = Orchestrator.create(); 259 | expectInterceptors(1,$,laneRunner);; 260 | $.task(task()).name("single").ret(1).get(); 261 | String got = laneRunner.runners.get(0).format(); 262 | assertTrue(got.contains("0| single.ret")); 263 | } 264 | } 265 | 266 | @Test 267 | public void installMulti() throws Exception { 268 | try (LaneRunner laneRunner = GlobalOrchestratorConfig.interceptLastOnCreate(ProfilingTaskRunner::new)) { 269 | Orchestrator $1 = Orchestrator.create(); 270 | expectInterceptors(1,$1,laneRunner);; 271 | $1.task(task()).name("single").ret(1).get(); 272 | Orchestrator $2 = Orchestrator.create(); 273 | expectInterceptors(2,$2,laneRunner);; 274 | $2.task(task()).name("single").ret(1).get(); 275 | String got1 = laneRunner.runners.get(0).format(); 276 | assertTrue(got1.contains("0| single.ret")); 277 | String got2 = laneRunner.runners.get(1).format(); 278 | assertTrue(got2.contains("0| single.ret")); 279 | } 280 | } 281 | 282 | @Test 283 | public void instalNested() throws Exception { 284 | try (LaneRunner laneRunner1 = GlobalOrchestratorConfig.interceptLastOnCreate(ProfilingTaskRunner::new)) { 285 | try (LaneRunner laneRunner2 = GlobalOrchestratorConfig.interceptLastOnCreate(ProfilingTaskRunner::new)) { 286 | Orchestrator $ = Orchestrator.create(); 287 | assertEquals(2, $.getNumberOfInterceptors()); 288 | assertEquals(1, laneRunner1.runners.size()); 289 | assertEquals(1, laneRunner2.runners.size()); 290 | } 291 | try (LaneRunner laneRunner2 = GlobalOrchestratorConfig.interceptLastOnCreate(ProfilingTaskRunner::new)) { 292 | Orchestrator $ = Orchestrator.create(); 293 | assertEquals(2, $.getNumberOfInterceptors()); 294 | assertEquals(2, laneRunner1.runners.size()); 295 | assertEquals(1, laneRunner2.runners.size()); 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/runners/StatTaskRunnerTest.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.BaseOrchestratorTest; 20 | import org.junit.Test; 21 | 22 | import java.util.concurrent.CompletableFuture; 23 | 24 | import static com.ebay.bascomtask.core.UberTask.task; 25 | import static org.junit.Assert.assertEquals; 26 | import static org.junit.Assert.assertTrue; 27 | 28 | /** 29 | * Tests StatTaskRunner. 30 | * 31 | * @author Brendan McCarthy 32 | */ 33 | public class StatTaskRunnerTest extends BaseOrchestratorTest { 34 | 35 | private final static MockTaskRun R1 = new MockTaskRun("bear", "hibernate"); 36 | private final static MockTaskRun R2 = new MockTaskRun("bear", "run"); 37 | 38 | @Test 39 | public void empty() { 40 | StatTaskRunner runner = new StatTaskRunner(); 41 | String report = runner.report(); 42 | System.out.println(report); 43 | String exp = "------------------------------------\n" 44 | + "| Count | Avg | Min | Max | Method |\n" 45 | + "|-------|-----|-----|-----|--------|\n" 46 | + "------------------------------------\n"; 47 | assertEquals(exp, report); 48 | } 49 | 50 | @Test 51 | public void oneRowOneTime() { 52 | StatTaskRunner runner = new StatTaskRunner(); 53 | R1.sim(runner, 0, 10); 54 | 55 | String report = runner.report(); 56 | System.out.println(report); 57 | String exp = "--------------------------------------------\n" 58 | + "| Count | Avg | Min | Max | Method |\n" 59 | + "|-------|-----|-----|-----|----------------|\n" 60 | + "|1 |10 |10 |10 |bear.hibernate |\n" 61 | + "--------------------------------------------\n"; 62 | assertEquals(exp, report); 63 | } 64 | 65 | @Test 66 | public void oneRowOneTimeExtended() { 67 | StatTaskRunner runner = new StatTaskRunner(); 68 | R1.sim(runner, 0, 10, 12); 69 | 70 | String report = runner.report(); 71 | System.out.println(report); 72 | assertTrue(report.contains("|1 |10+2 |10+2 |10+2 |bear.hibernate |")); 73 | } 74 | 75 | @Test 76 | public void oneRowTwoTimes() { 77 | StatTaskRunner runner = new StatTaskRunner(); 78 | R1.sim(runner, 0, 10); 79 | R1.sim(runner, 5, 45); 80 | 81 | String report = runner.report(); 82 | System.out.println(report); 83 | assertTrue(report.contains("|2 |25 |10 |40 |bear.hibernate |")); 84 | } 85 | 86 | @Test 87 | public void twoRowsOneTime() { 88 | StatTaskRunner runner = new StatTaskRunner(); 89 | R1.sim(runner, 0, 10); 90 | R2.sim(runner, 5, 45); 91 | 92 | String report = runner.report(); 93 | System.out.println(report); 94 | assertTrue(report.contains("|1 |10 |10 |10 |bear.hibernate |")); 95 | assertTrue(report.contains("|1 |40 |40 |40 |bear.run |")); 96 | } 97 | 98 | @Test 99 | public void oneRowManyTimes() { 100 | StatTaskRunner runner = new StatTaskRunner(); 101 | for (int i = 0; i < 999999; i++) { 102 | long start = i % 100; 103 | long end = start + 1000 + i % 101; 104 | R1.sim(runner, start, end); 105 | } 106 | 107 | String report = runner.report(); 108 | System.out.println(report); 109 | assertTrue(report.contains("|999999 |1050 |1000 |1100 |bear.hibernate |")); 110 | } 111 | 112 | @Test 113 | public void oneRunner() throws Exception { 114 | StatTaskRunner taskRunner = new StatTaskRunner(); 115 | $.firstInterceptWith(taskRunner); 116 | for (int i = 0; i < 3; i++) { 117 | CompletableFuture c = $.task(task().delayFor(30)).name("blue").ret(1); 118 | CompletableFuture inc = $.task(task().delayFor(20)).name("red").inc(c); 119 | inc.get(); 120 | } 121 | String report = taskRunner.report(); 122 | System.out.println(report); 123 | assertTrue(report.contains("|3 |")); 124 | assertTrue(report.contains("|red.inc |")); 125 | assertTrue(report.contains("|blue.ret |")); 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/timings/GraphVariations.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.timings; 18 | 19 | import com.ebay.bascomtask.core.Orchestrator; 20 | 21 | import java.util.concurrent.CompletableFuture; 22 | 23 | /** 24 | * Some graph variations for testing. 25 | * 26 | * @author Brendan McCarthy 27 | */ 28 | public class GraphVariations { 29 | 30 | private static LongOperationsTask task() { 31 | return new LongOperationsTask.LongOperationsTaskImpl(); 32 | } 33 | 34 | private static T get(CompletableFuture cf) { 35 | try { 36 | return cf.get(); 37 | } catch (Exception e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | public static long diamond(long input) { 43 | Orchestrator $ = Orchestrator.create("diamond"); 44 | 45 | CompletableFuture base = $.task(task()).ret(input); 46 | CompletableFuture left = $.task(task()).inc(base); 47 | CompletableFuture right = $.task(task()).inc(base); 48 | CompletableFuture bottom = $.task(task()).add(left, right); 49 | 50 | return get(bottom); 51 | } 52 | 53 | public static long grid3x3(long input) { 54 | Orchestrator $ = Orchestrator.create("grid3x3"); 55 | 56 | CompletableFuture ul = $.task(task()).ret(input); 57 | CompletableFuture um = $.task(task()).ret(input); 58 | CompletableFuture ur = $.task(task()).ret(input); 59 | CompletableFuture ml = $.task(task()).add(ul, um, ur); 60 | CompletableFuture mm = $.task(task()).add(ul, um, ur); 61 | CompletableFuture mr = $.task(task()).add(ul, um, ur); 62 | CompletableFuture bl = $.task(task()).add(ml, mm, mr); 63 | CompletableFuture bm = $.task(task()).add(ml, mm, mr); 64 | CompletableFuture br = $.task(task()).add(ml, mm, mr); 65 | 66 | $.activateAndWait(bl, bm, br); 67 | 68 | return get(bl) + get(bm) + get(br); 69 | } 70 | 71 | public static long stacks(long input) { 72 | Orchestrator $ = Orchestrator.create("diamond"); 73 | 74 | CompletableFuture left1 = $.task(task()).ret(input); // v 75 | CompletableFuture left2 = $.task(task()).inc(left1); // v+1 76 | CompletableFuture left3 = $.task(task()).add(left1, left2); // v+v+1 = 2v+1 77 | CompletableFuture left4 = $.task(task()).add(left2, left3); // (v+1) + (2v+1) = 3v+2 78 | CompletableFuture left5 = $.task(task()).add(left2, left3, left4); // v+1 + 2v + 1 + 3v +2 = 6v+ 4 79 | 80 | CompletableFuture right1 = $.task(task()).ret(input); 81 | CompletableFuture right2 = $.task(task()).inc(right1); 82 | CompletableFuture right3 = $.task(task()).add(right1, right2); 83 | CompletableFuture right4 = $.task(task()).add(right2, right3); 84 | CompletableFuture right5 = $.task(task()).add(right2, right3, right4); 85 | 86 | CompletableFuture left6 = $.task(task()).add(left3, right5); // 2v+1 + 6v+4 = 8v+5 87 | CompletableFuture right6 = $.task(task()).add(right3, left5); // 88 | CompletableFuture add = $.task(task()).add(left6, right6); 89 | 90 | // Start various combinations first for variation in graph flow 91 | switch (((int) input % 5) + 1) { 92 | case 1: 93 | $.activate(left1, right1); 94 | break; 95 | case 2: 96 | $.activate(left2, right2); 97 | break; 98 | case 3: 99 | $.activate(left3, right3); 100 | break; 101 | case 4: 102 | $.activate(left4, right4); 103 | break; 104 | case 5: 105 | $.activate(left5, right5); 106 | break; 107 | default: // do nothing 108 | } 109 | 110 | return get(add); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/timings/LongOperationsTask.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.timings; 18 | 19 | import com.ebay.bascomtask.core.TaskInterface; 20 | 21 | import java.util.concurrent.CompletableFuture; 22 | 23 | /** 24 | * Task with simple arithmetic operations. 25 | * 26 | * @author Brendan McCarthy 27 | */ 28 | public interface LongOperationsTask extends TaskInterface { 29 | CompletableFuture ret(long v); 30 | 31 | CompletableFuture inc(CompletableFuture cf); 32 | 33 | CompletableFuture add(CompletableFuture cf1, CompletableFuture cf2); 34 | 35 | CompletableFuture add(CompletableFuture cf1, CompletableFuture cf2, CompletableFuture cf3); 36 | 37 | class LongOperationsTaskImpl implements LongOperationsTask { 38 | 39 | @Override 40 | public CompletableFuture ret(long v) { 41 | return complete(v); 42 | } 43 | 44 | @Override 45 | public CompletableFuture inc(CompletableFuture cf) { 46 | long v = get(cf); 47 | return complete(v + 1); 48 | } 49 | 50 | @Override 51 | public CompletableFuture add(CompletableFuture cf1, CompletableFuture cf2) { 52 | long x = get(cf1); 53 | long y = get(cf2); 54 | return complete(x + y); 55 | 56 | } 57 | 58 | @Override 59 | public CompletableFuture add(CompletableFuture cf1, CompletableFuture cf2, CompletableFuture cf3) { 60 | long x = get(cf1); 61 | long y = get(cf2); 62 | long z = get(cf3); 63 | return complete(x + y + z); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/timings/TimingTest.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.timings; 18 | 19 | import ch.qos.logback.classic.Level; 20 | import ch.qos.logback.classic.LoggerContext; 21 | import org.junit.AfterClass; 22 | import org.junit.BeforeClass; 23 | import org.junit.Test; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import java.util.function.Function; 28 | 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertTrue; 31 | 32 | /** 33 | * Timing tests to ensure that multiple loops of various graph combinations do not take an 34 | * unexpected amount of time. 35 | * 36 | * @author Brendan McCarthy 37 | */ 38 | public class TimingTest { 39 | private static final Logger LOG = LoggerFactory.getLogger(TimingTest.class); 40 | 41 | private static int LOOP_COUNT = 1000; 42 | 43 | public static Level levelSave; 44 | 45 | @BeforeClass 46 | public static void beforeClass() { 47 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 48 | ch.qos.logback.classic.Logger logger = loggerContext.getLogger("root"); 49 | levelSave = logger.getLevel(); 50 | // Avoid debug logging because we're looping thousands of times here 51 | logger.setLevel(Level.INFO); 52 | } 53 | 54 | @AfterClass 55 | public static void afterClass() { 56 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 57 | ch.qos.logback.classic.Logger logger = loggerContext.getLogger("root"); 58 | logger.setLevel(levelSave); 59 | } 60 | 61 | /** 62 | * Runs fn repeatedly after a warmup, testing to ensure that there's no egregious slowness. 63 | * Typically the average max should be much lower that the supplied expectedMaxMs to avoid failure for an 64 | * occasional system overload irregularity. 65 | * 66 | * @param expectedMaxMs verify that avg for all runs does not exceed this 67 | * @param loc description to include in log output 68 | * @param fn to run on eac loop 69 | * @param expecting verify each run 70 | */ 71 | private void run(double expectedMaxMs, String loc, Function fn, Function expecting) { 72 | for (long i = 100; i < LOOP_COUNT; i++) { // Warmup 73 | fn.apply(i); 74 | } 75 | long start = System.nanoTime(); 76 | for (long i = 0; i < LOOP_COUNT; i++) { 77 | long got = fn.apply(i); 78 | long exp = expecting.apply(i); 79 | if (exp != got) { 80 | assertEquals("Loop#" + i, exp, got); 81 | } 82 | } 83 | long duration = System.nanoTime() - start; 84 | double avg = (duration / (float) LOOP_COUNT) / 1000000; 85 | String msg = String.format("Avg time for %d loops on \"%s\": %.2fms%n", LOOP_COUNT, loc, avg); 86 | LOG.info(msg); 87 | assertTrue(avg < expectedMaxMs); 88 | } 89 | 90 | @Test 91 | public void diamondTest() { 92 | run(1, "diamond", GraphVariations::diamond, v -> (v + 1) * 2); 93 | } 94 | 95 | @Test 96 | public void gridTest() { 97 | run(1.5, "grid3x3", GraphVariations::grid3x3, v -> v * 27); 98 | } 99 | 100 | @Test 101 | public void stacksTest() { 102 | run(2, "stacks", GraphVariations::stacks, v -> v * 16 + 10); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/java/com/ebay/bascomtask/util/CommonTestingUtils.java: -------------------------------------------------------------------------------- 1 | package com.ebay.bascomtask.util; 2 | 3 | import com.ebay.bascomtask.core.TaskMeta; 4 | 5 | import static org.junit.Assert.assertTrue; 6 | 7 | public class CommonTestingUtils { 8 | 9 | public static void validateTimes(TaskMeta meta) { 10 | assertTrue(meta.getStartedAt() > 0); 11 | assertTrue(meta.getEndedAt() > 0); 12 | assertTrue(meta.getCompletedAt() > 0); 13 | assertTrue(meta.getEndedAt() >= meta.getStartedAt()); 14 | assertTrue(meta.getCompletedAt() >= meta.getCompletedAt()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bascomtask-core/src/test/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eBay/bascomtask/d9194162bf49de942226fa120db50f76bbfaa5a3/bascomtask-core/src/test/resources/application.properties -------------------------------------------------------------------------------- /doc/Changelog.md: -------------------------------------------------------------------------------- 1 | # BascomTask Changes Since Version 1 2 | 3 | ## v2.0 4 | 1. Significant rewrite that dropped support for implicit argument matching in favor of direct wiring. 5 | 6 | ## v2.1 7 | 1. Renamed 'execute' (now deprecated) to 'activate' methods on Orchestrator in order to clarify concepts. 8 | 2. Added Orchestrator.activateFuture(), which facilitates working asynchronously with a uniform set of items. 9 | 3. Added a variant of Orchestrator.activateWait() that works likewise with a uniform set of items. 10 | 4. Added Orchestrator.activateAsReady() for streaming results. 11 | 5. Limited activation on CompletableFutures to value-access methods only, such as get(), join(). 12 | 6. Added activate() methods on Orchestrator and TaskWrapper for easy explicit activation. 13 | 7. Replaced ThreadLocalRunner with LaneRunner which for managing TaskRunners works more consistently and easily against asynchronous execution. 14 | 8. Return parameterized Optional from cond/2 rather than void. 15 | 9. Added Orchestrator.current() for accessing current Orchestrator from within a task method. 16 | 17 | ## v2.2 18 | 1. Exposed isLight on TaskRun so runners can make decisions based on this property 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.ebay.bascomtask 6 | bascomtask-project 7 | 2.2-SNAPSHOT 8 | pom 9 | 10 | ${project.groupId}:${project.artifactId} 11 | Java lightweight parallel task orchestration 12 | https://github.com/eBay/bascomtask 13 | 14 | 15 | 16 | Apache License, Version 2.0 17 | https://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | 23 | https://github.com/eBay/bascomtask.git 24 | https://github.com/eBay/bascomtask.git 25 | master 26 | https://github.com/eBay/bascomtask 27 | 28 | 29 | 30 | 31 | bremccarthy 32 | Brendan McCarthy 33 | bremccarthy@ebay.com 34 | eBay 35 | http://www.ebay.com 36 | 37 | architect 38 | developer 39 | 40 | America/San_Francisco 41 | 42 | 43 | 44 | 45 | UTF-8 46 | 1.8 47 | 1.8 48 | 49 | 50 | 51 | bascomtask-core 52 | 53 | 54 | 55 | 56 | 57 | org.sonatype.plugins 58 | nexus-staging-maven-plugin 59 | 1.6.8 60 | true 61 | 62 | ossrh 63 | https://oss.sonatype.org/ 64 | 65 | true 66 | 67 | 68 | 69 | com.github.spotbugs 70 | spotbugs-maven-plugin 71 | 3.1.3 72 | 73 | 74 | com.github.spotbugs 75 | spotbugs 76 | 3.1.3 77 | 78 | 79 | 80 | 81 | org.pitest 82 | pitest-maven 83 | 1.6.2 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-source-plugin 88 | 3.0.1 89 | 90 | 91 | attach-sources 92 | 93 | jar-no-fork 94 | 95 | 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-javadoc-plugin 101 | 3.0.0 102 | 103 | 104 | attach-javadocs 105 | 106 | jar 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | sign 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-gpg-plugin 123 | 1.6 124 | 125 | 126 | sign-artifacts 127 | verify 128 | 129 | sign 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | ossrh 142 | https://oss.sonatype.org/content/repositories/snapshots 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /spell.txt: -------------------------------------------------------------------------------- 1 | bascomtask 2 | dataflow 3 | orchestrator 4 | exec 5 | concatenator 6 | hardwired 7 | microservices 8 | orchestration 9 | datastores 10 | --------------------------------------------------------------------------------