Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Villiger2019-08-26 09:29:09 +0000
committerMatthias Villiger2019-08-26 09:29:09 +0000
commitb9d1080bcbac032dd92e50d5a4bcecdc9ae6542a (patch)
tree5df12f118fd404ead469f851be67f75bbccf20a4
parentfa72e93b20459e9622d4d09aad443be664ba25eb (diff)
downloadorg.eclipse.scout.sdk-b9d1080bcbac032dd92e50d5a4bcecdc9ae6542a.tar.gz
org.eclipse.scout.sdk-b9d1080bcbac032dd92e50d5a4bcecdc9ae6542a.tar.xz
org.eclipse.scout.sdk-b9d1080bcbac032dd92e50d5a4bcecdc9ae6542a.zip
IFuture result computation should be lazy
The result computation of IFutures should only be performed if the client requests the result. If it just waits for the computation to complete, the result must not be computed. Therefore the result is always of type Supplier so that the computation of the supplier value is only performed on request and only once.
-rw-r--r--org.eclipse.scout.sdk.core.s.test/src/test/java/org/eclipse/scout/sdk/core/s/environment/CompletedFutureTest.java27
-rw-r--r--org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/Future.java14
-rw-r--r--org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/IFuture.java14
-rw-r--r--org.eclipse.scout.sdk.s2e.test/src/test/java/org/eclipse/scout/sdk/s2e/environment/JobFutureTest.java49
-rw-r--r--org.eclipse.scout.sdk.s2e/src/main/java/org/eclipse/scout/sdk/s2e/environment/JobFuture.java4
5 files changed, 73 insertions, 35 deletions
diff --git a/org.eclipse.scout.sdk.core.s.test/src/test/java/org/eclipse/scout/sdk/core/s/environment/CompletedFutureTest.java b/org.eclipse.scout.sdk.core.s.test/src/test/java/org/eclipse/scout/sdk/core/s/environment/CompletedFutureTest.java
index ba25adcf7..c46431937 100644
--- a/org.eclipse.scout.sdk.core.s.test/src/test/java/org/eclipse/scout/sdk/core/s/environment/CompletedFutureTest.java
+++ b/org.eclipse.scout.sdk.core.s.test/src/test/java/org/eclipse/scout/sdk/core/s/environment/CompletedFutureTest.java
@@ -10,20 +10,16 @@
*/
package org.eclipse.scout.sdk.core.s.environment;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
/**
* <h3>{@link CompletedFutureTest}</h3>
*
@@ -38,11 +34,14 @@ public class CompletedFutureTest {
assertFalse(f.isCancelled());
assertFalse(f.isCompletedExceptionally());
assertTrue(f.isDone());
- assertEquals(input, f.get());
- assertEquals(input, f.get(1, TimeUnit.SECONDS));
+ assertEquals(input, f.get().get());
+ assertEquals(input, f.result());
+ assertEquals(input, f.get(1, TimeUnit.SECONDS).get());
StringBuilder done = new StringBuilder();
- f.thenAccept(done::append);
+ f
+ .thenApply(Supplier::get)
+ .thenAccept(done::append);
f.awaitDoneThrowingOnErrorOrCancel();
assertEquals(input, done.toString());
}
@@ -50,7 +49,8 @@ public class CompletedFutureTest {
@Test
public void testNoResult() throws InterruptedException, ExecutionException {
IFuture<String> f = Future.completed(null);
- assertNull(f.get());
+ assertNull(f.get().get());
+ assertNull(f.result());
}
@Test
@@ -74,7 +74,10 @@ public class CompletedFutureTest {
String input = "abc";
StringBuilder done = new StringBuilder();
- f.exceptionally(ex -> done.append(input));
+ f.exceptionally(ex -> {
+ done.append(input);
+ return () -> null;
+ });
assertEquals(input, done.toString());
assertSame(t, assertThrows(RuntimeException.class, f::awaitDoneThrowingOnErrorOrCancel).getCause());
diff --git a/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/Future.java b/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/Future.java
index 605b7dcb7..ffd1197db 100644
--- a/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/Future.java
+++ b/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/Future.java
@@ -16,13 +16,14 @@ import java.util.concurrent.CompletionException;
import java.util.function.Supplier;
import org.eclipse.scout.sdk.core.log.SdkLog;
+import org.eclipse.scout.sdk.core.util.FinalValue;
/**
* <h3>{@link Future}</h3>
*
* @since 7.1.0
*/
-public class Future<V> extends CompletableFuture<V> implements IFuture<V> {
+public class Future<V> extends CompletableFuture<Supplier<V>> implements IFuture<V> {
/**
* Creates a completed {@link IFuture} with the specified result.
@@ -93,10 +94,10 @@ public class Future<V> extends CompletableFuture<V> implements IFuture<V> {
@Override
public Future<V> awaitDoneThrowingOnError() {
try {
- result();
+ join();
}
catch (CancellationException e) {
- SdkLog.debug("Canellation silently ignored", e);
+ SdkLog.debug("Cancellation silently ignored", e);
}
return this;
}
@@ -108,10 +109,11 @@ public class Future<V> extends CompletableFuture<V> implements IFuture<V> {
else {
if (error == null) {
if (resultExtractor == null) {
- complete(null);
+ complete(() -> null); // the supplier should never be null. only the result provided by the supplier may be null
}
else {
- complete(resultExtractor.get());
+ FinalValue<V> cachedResult = new FinalValue<>();
+ complete(() -> cachedResult.computeIfAbsentAndGet(resultExtractor));
}
}
else {
@@ -124,7 +126,7 @@ public class Future<V> extends CompletableFuture<V> implements IFuture<V> {
@Override
public V result() {
try {
- return join();
+ return join().get();
}
catch (CompletionException e) {
SdkLog.debug("Future completed with errors.", e);
diff --git a/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/IFuture.java b/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/IFuture.java
index 952008543..72b6aa5af 100644
--- a/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/IFuture.java
+++ b/org.eclipse.scout.sdk.core.s/src/main/java/org/eclipse/scout/sdk/core/s/environment/IFuture.java
@@ -15,6 +15,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
+import java.util.function.Supplier;
/**
* <h3>{@link IFuture}</h3>
@@ -25,7 +26,7 @@ import java.util.concurrent.Future;
* @see Future
* @see CompletionStage
*/
-public interface IFuture<V> extends Future<V>, CompletionStage<V> {
+public interface IFuture<V> extends Future<Supplier<V>>, CompletionStage<Supplier<V>> {
/**
* Returns the result value when complete, or throws an (unchecked) exception if completed exceptionally. To better
@@ -36,16 +37,17 @@ public interface IFuture<V> extends Future<V>, CompletionStage<V> {
* If the original (unchecked) exception should be thrown instead of a wrapping {@link CompletionException}, use
* {@link #result()} instead.
*
- * @return the result value
+ * @return A {@link Supplier} that returns the result value
* @throws CancellationException
* if the computation was cancelled. See {@link #cancel(boolean)} for more details.
* @throws CompletionException
* if this future completed exceptionally or a completion computation threw an exception.
*/
- V join();
+ Supplier<V> join();
/**
- * Returns the result value (or throws any encountered exception) if completed, else returns the given valueIfAbsent.
+ * Returns the result value (or throws any encountered exception) if completed, else returns the given valueIfAbsent
+ * {@link Supplier}.
*
* @param valueIfAbsent
* the value to return if not completed
@@ -56,7 +58,7 @@ public interface IFuture<V> extends Future<V>, CompletionStage<V> {
* if this future completed exceptionally or a completion computation threw an exception
* @see #join()
*/
- V getNow(V valueIfAbsent);
+ Supplier<V> getNow(Supplier<V> valueIfAbsent);
/**
* Returns {@code true} if this {@link IFuture} completed exceptionally, in any way. Possible causes include
@@ -107,7 +109,7 @@ public interface IFuture<V> extends Future<V>, CompletionStage<V> {
V result();
/**
- * Same as {@link #result()} but returns this {@link IFuture} instead of the result value.
+ * Same as {@link #join()} but returns this {@link IFuture} instead of the result value.
*
* @return this
*/
diff --git a/org.eclipse.scout.sdk.s2e.test/src/test/java/org/eclipse/scout/sdk/s2e/environment/JobFutureTest.java b/org.eclipse.scout.sdk.s2e.test/src/test/java/org/eclipse/scout/sdk/s2e/environment/JobFutureTest.java
index 2038df9b1..73333877b 100644
--- a/org.eclipse.scout.sdk.s2e.test/src/test/java/org/eclipse/scout/sdk/s2e/environment/JobFutureTest.java
+++ b/org.eclipse.scout.sdk.s2e.test/src/test/java/org/eclipse/scout/sdk/s2e/environment/JobFutureTest.java
@@ -10,14 +10,6 @@
*/
package org.eclipse.scout.sdk.s2e.environment;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -28,6 +20,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -39,6 +32,8 @@ import org.eclipse.scout.sdk.core.util.SdkException;
import org.eclipse.scout.sdk.s2e.S2ESdkActivator;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
/**
* <h3>{@link JobFutureTest}</h3>
*
@@ -253,6 +248,32 @@ public class JobFutureTest {
future.awaitDoneThrowingOnErrorOrCancel();
}
+ @Test
+ public void testLazyResult() throws ExecutionException, InterruptedException {
+ AtomicInteger resultSupplierInvokeCounter = new AtomicInteger();
+ String expectedResult = "test result";
+ Supplier<String> resultSupplier = () -> {
+ resultSupplierInvokeCounter.incrementAndGet();
+ return expectedResult;
+ };
+ JobFuture<String> jf = createFixtureJobFuture(resultSupplier);
+ assertEquals(0, resultSupplierInvokeCounter.get());
+ jf.job().schedule();
+
+ jf.get(); // wait until completed
+ assertEquals(0, resultSupplierInvokeCounter.get()); // resultSupplier must not yet been executed because the result has not yet been retrieved (lazy computation)
+
+ assertEquals(expectedResult, jf.result()); // here the resultSupplier is invoked
+ assertEquals(1, resultSupplierInvokeCounter.get());
+
+ String result = jf.get().get();
+ assertEquals(expectedResult, result); // here the resultSupplier is invoked
+ assertSame(result, jf.result());
+
+ // even the result was requested multiple times, it has only been computed once
+ assertEquals(1, resultSupplierInvokeCounter.get());
+ }
+
private static JobFuture<String> createFixtureJobFuture(String result) {
return createFixtureJobFuture(result, () -> {
});
@@ -263,7 +284,17 @@ public class JobFutureTest {
return createFixtureJobFuture(j);
}
+ private static JobFuture<String> createFixtureJobFuture(Supplier<String> resultSupplier) {
+ AbstractJob j = new RunnableJob("", () -> {
+ });
+ return createFixtureJobFuture(j, resultSupplier);
+ }
+
private static JobFuture<String> createFixtureJobFuture(AbstractJob j) {
- return new JobFuture<>(j, () -> j.getResult() == null ? null : j.getName());
+ return createFixtureJobFuture(j, () -> j.getResult() == null ? null : j.getName());
+ }
+
+ private static JobFuture<String> createFixtureJobFuture(AbstractJob j, Supplier<String> resultSupplier) {
+ return new JobFuture<>(j, resultSupplier);
}
}
diff --git a/org.eclipse.scout.sdk.s2e/src/main/java/org/eclipse/scout/sdk/s2e/environment/JobFuture.java b/org.eclipse.scout.sdk.s2e/src/main/java/org/eclipse/scout/sdk/s2e/environment/JobFuture.java
index f54216920..e7ad17d0d 100644
--- a/org.eclipse.scout.sdk.s2e/src/main/java/org/eclipse/scout/sdk/s2e/environment/JobFuture.java
+++ b/org.eclipse.scout.sdk.s2e/src/main/java/org/eclipse/scout/sdk/s2e/environment/JobFuture.java
@@ -100,7 +100,7 @@ public final class JobFuture<V> extends Future<V> {
* @param monitor
* the progress monitor that can be used to cancel the waiting, or {@code null} if cancellation is not
* required. No progress is reported on this monitor.
- * @return the computed result
+ * @return A {@link Supplier} that returns the computed result
* @throws CancellationException
* if the computation was cancelled
* @throws ExecutionException
@@ -110,7 +110,7 @@ public final class JobFuture<V> extends Future<V> {
* @throws TimeoutException
* if the wait timed out
*/
- public V get(long timeout, TimeUnit unit, IProgressMonitor monitor) throws InterruptedException, ExecutionException, TimeoutException {
+ public Supplier<V> get(long timeout, TimeUnit unit, IProgressMonitor monitor) throws InterruptedException, ExecutionException, TimeoutException {
try {
detectDeadLock();
boolean completed = m_job.join(unit.toMillis(timeout), monitor);

Back to the top