/******************************************************************************* * Copyright (c) 2016 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.utilbox.concurrency; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import melnorme.utilbox.core.fntypes.CallableX; /** * A future meant to be completed by an explicit {@link #setResult()} call. * Similar to {@link CompletableFuture} but with a safer and simplified API, particularly: * * - Uses the {@link BasicFuture} interface which has a safer and more precise API than {@link Future} * with regards to exception throwing. * - By default, completing the Future ({@link #setResult()}) can only be attempted once, * it is illegal for multiple {@link #setResult()} calls to be attempted. * - This future can also be completed with a RuntimeException, which will be thrown whenever * a client tries to obtain the result. Note that using RuntimeExceptions is discouraged. * The main use case for this functionality is to handle exceptions representing bugs (assertion failures, etc.) * in a way that is easier to debug the failure: by threwing them back closer to the point of origin of the bug, * as opposed to getting silently swallowed/ignored by an executor worker thread. * */ public class CompletableResult<DATA> implements BasicFuture<DATA> { protected final CountDownLatch terminationLatch = new CountDownLatch(1); protected final Object lock = new Object(); protected volatile ResultStatus status = ResultStatus.NOT_TERMINATED; protected volatile DATA result; protected volatile RuntimeException resultRuntimeException; public static enum ResultStatus { NOT_TERMINATED, RESULT_SET, CANCELLED } public CompletableResult() { super(); } @Override public boolean isTerminated() { return status != ResultStatus.NOT_TERMINATED; } @Override public boolean isCancelled() { return status == ResultStatus.CANCELLED; } protected CountDownLatch getTerminationLatch() { return terminationLatch; } @Override public void awaitTermination() throws InterruptedException { if(isTerminated()) { return; // Early check so that InterruptedException is not thrown } getTerminationLatch().await(); } @Override public void awaitTermination(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { if(isTerminated()) { return; // Early check so that InterruptedException is not thrown } boolean success = getTerminationLatch().await(timeout, unit); if(!success) { throw new TimeoutException(); } } public void setResultFromCallable(CallableX<DATA, RuntimeException> resultCallable) { try { DATA result = resultCallable.invoke(); setResult(result); } catch(RuntimeException re) { doSetResult(null, re); } } /** * Complete this future with given result. * NOTE: clients who obtain a result from a functional object like a {@link Callable} or similar, * should use {@link #setResultFromCallable(CallableX)} instead, in order to preserve RuntimeExceptions */ public void setResult(DATA result) { doSetResult(result, null); } protected void doSetResult(DATA result, RuntimeException re) { assertTrue(re == null || result == null); // Only one possible result synchronized (lock) { if(isTerminated()) { if(isCancelled()) { return; } handleReSetResult(); return; } this.result = result; this.resultRuntimeException = re; status = ResultStatus.RESULT_SET; terminationLatch.countDown(); } } /** * Set the result of this {@link CompletableResult} as cancelled. * Has no effect if a result has already been set. * * Note: if there is still a task or background task calculating a result, it is the caller's responsibility * to terminate that task, this {@link CompletableResult} knows nothing about it. */ public boolean setCancelledResult() { synchronized (lock) { if(isTerminated()) { return false; } else { status = ResultStatus.CANCELLED; terminationLatch.countDown(); return true; } } } protected void handleReSetResult() { throw assertFail(); } @Override public DATA getResult_forSuccessfulyCompleted() { assertTrue(isCompletedSuccessfully()); if(resultRuntimeException != null) { throw resultRuntimeException; } return result; } /* ----------------- ----------------- */ public static class CompletableLatch extends CompletableResult<Object> { public void setCompleted() { setResult(null); } @Override protected void handleReSetResult() { // Do nothing - this is allowed because the possible value is always null anyways } } }