Skip to content

Commit f3353de

Browse files
authored
Performance improvement for Future.await() (#6188)
* Performance improvement for Future.await() See #5402 Before this change, Future.await() always relied on a CountDownLatch, regardless of the Future type or its state. This commit improves the implementation for: - SucceededFuture (return result) - FailedFuture (throw failure) - Future (check state and use a CountDownLatch only when necessary) Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Clarify Future#await Javadoc and expand testing Signed-off-by: Thomas Segismont <tsegismont@gmail.com> --------- Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent ac0f526 commit f3353de

6 files changed

Lines changed: 309 additions & 61 deletions

File tree

vertx-core/src/main/java/io/vertx/core/Future.java

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
package io.vertx.core;
1313

1414
import io.vertx.codegen.annotations.GenIgnore;
15-
import io.vertx.core.impl.WorkerExecutor;
16-
import io.vertx.core.internal.ContextInternal;
1715
import io.vertx.core.impl.Utils;
16+
import io.vertx.core.impl.WorkerExecutor;
1817
import io.vertx.core.impl.future.CompositeFutureImpl;
1918
import io.vertx.core.impl.future.FailedFuture;
2019
import io.vertx.core.impl.future.SucceededFuture;
20+
import io.vertx.core.internal.ContextInternal;
2121

2222
import java.time.Duration;
2323
import java.util.List;
@@ -707,6 +707,9 @@ static <T> Future<T> fromCompletionStage(CompletionStage<T> completionStage, Con
707707
}
708708

709709
private CountDownLatch trySuspend() {
710+
if (isComplete()) {
711+
return null;
712+
}
710713
io.vertx.core.impl.WorkerExecutor executor = io.vertx.core.impl.WorkerExecutor.unwrapWorkerExecutor();
711714
CountDownLatch latch;
712715
if (executor != null) {
@@ -733,18 +736,12 @@ private T getOrFail() {
733736
}
734737

735738
/**
736-
* Park the current thread until the {@code future} is completed, when the future
737-
* is completed the thread is un-parked and
738-
*
739-
* <ul>
740-
* <li>the result value is returned when the future was completed with a result</li>
741-
* <li>otherwise, the failure is thrown</li>
742-
* </ul>
743-
*
744-
* This method must be called from a vertx virtual thread or a non vertx thread.
739+
* If this {@link Future} is already completed or failed, then this method immediately returns the result or throws the failure, respectively.
740+
* <p>
741+
* Otherwise, the current thread is parked until this {@link Future} is completed or failed.
745742
*
746-
* @return the result
747-
* @throws IllegalStateException when called from a vertx event-loop or worker thread
743+
* @return the result when this {@link Future} is completed
744+
* @throws IllegalStateException when the current thread must be parked and this method is called from a Vert.x event-loop or worker thread.
748745
*/
749746
default T await() {
750747
CountDownLatch continuation = trySuspend();
@@ -763,9 +760,9 @@ default T await() {
763760
* Like {@link #await()} but with a timeout.
764761
*
765762
* @param timeout the timeout
766-
* @return the result
763+
* @return the result when this {@link Future} is completed
767764
* @throws TimeoutException when the timeout fires before the future completes
768-
* @throws IllegalStateException when called from a vertx event-loop or worker thread
765+
* @throws IllegalStateException when the current thread must be parked and this method is called from a Vert.x event-loop or worker thread.
769766
*/
770767
@GenIgnore(GenIgnore.PERMITTED_TYPE)
771768
default T await(Duration timeout) throws TimeoutException {
@@ -777,9 +774,9 @@ default T await(Duration timeout) throws TimeoutException {
777774
*
778775
* @param timeout the timeout
779776
* @param unit the timeout unit
780-
* @return the result
777+
* @return the result when this {@link Future} is completed
781778
* @throws TimeoutException when the timeout fires before the future completes
782-
* @throws IllegalStateException when called from a vertx event-loop or worker thread
779+
* @throws IllegalStateException when the current thread must be parked and this method is called from a Vert.x event-loop or worker thread.
783780
*/
784781
default T await(long timeout, TimeUnit unit) throws TimeoutException {
785782
if (unit == null) {

vertx-core/src/main/java/io/vertx/core/impl/future/FailedFuture.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
import io.vertx.core.Completable;
1515
import io.vertx.core.Future;
1616
import io.vertx.core.Handler;
17-
import io.vertx.core.internal.ContextInternal;
1817
import io.vertx.core.impl.NoStackTraceThrowable;
18+
import io.vertx.core.impl.Utils;
19+
import io.vertx.core.internal.ContextInternal;
1920

21+
import java.time.Duration;
22+
import java.util.concurrent.TimeUnit;
23+
import java.util.concurrent.TimeoutException;
2024
import java.util.function.Function;
2125

2226
/**
@@ -125,6 +129,24 @@ public Future<T> otherwise(T value) {
125129
return new SucceededFuture<>(context, value);
126130
}
127131

132+
@Override
133+
public T await() {
134+
Utils.throwAsUnchecked(cause());
135+
return null;
136+
}
137+
138+
@Override
139+
public T await(Duration timeout) throws TimeoutException {
140+
Utils.throwAsUnchecked(cause());
141+
return null;
142+
}
143+
144+
@Override
145+
public T await(long timeout, TimeUnit unit) throws TimeoutException {
146+
Utils.throwAsUnchecked(cause());
147+
return null;
148+
}
149+
128150
@Override
129151
public String toString() {
130152
return "Future{cause=" + cause.getMessage() + "}";

vertx-core/src/main/java/io/vertx/core/impl/future/SucceededFuture.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
import io.vertx.core.Handler;
1717
import io.vertx.core.internal.ContextInternal;
1818

19+
import java.time.Duration;
1920
import java.util.Objects;
21+
import java.util.concurrent.TimeUnit;
22+
import java.util.concurrent.TimeoutException;
2023
import java.util.function.Function;
2124

2225
/**
@@ -116,6 +119,21 @@ public Future<T> otherwise(T value) {
116119
return this;
117120
}
118121

122+
@Override
123+
public T await() {
124+
return result();
125+
}
126+
127+
@Override
128+
public T await(Duration timeout) throws TimeoutException {
129+
return result();
130+
}
131+
132+
@Override
133+
public T await(long timeout, TimeUnit unit) throws TimeoutException {
134+
return result();
135+
}
136+
119137
@Override
120138
public String toString() {
121139
return "Future{result=" + result + "}";
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
12+
package io.vertx.benchmarks;
13+
14+
import io.vertx.core.Future;
15+
import io.vertx.core.Promise;
16+
import org.openjdk.jmh.annotations.*;
17+
18+
import java.util.concurrent.TimeUnit;
19+
20+
@Warmup(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS)
21+
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
22+
@Threads(1)
23+
@Fork(5)
24+
@BenchmarkMode(Mode.Throughput)
25+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
26+
@State(Scope.Benchmark)
27+
public class FutureAwaitBenchmark {
28+
29+
private final Future<String> succeededFuture = Future.succeededFuture("foo");
30+
private final Future<String> failedFuture = Future.failedFuture("foo");
31+
private final Future<String> ofCompletedPromise;
32+
private final Future<String> ofFailedPromise;
33+
34+
public FutureAwaitBenchmark() {
35+
Promise<String> p1 = Promise.promise();
36+
p1.complete("foo");
37+
ofCompletedPromise = p1.future();
38+
39+
Promise<String> p2 = Promise.promise();
40+
p2.fail("foo");
41+
ofFailedPromise = p2.future();
42+
}
43+
44+
@Benchmark
45+
public String succeededFuture() {
46+
return succeededFuture.await();
47+
}
48+
49+
@Benchmark
50+
public Exception failedFuture() {
51+
try {
52+
failedFuture.await();
53+
throw new AssertionError("Should have thrown exception");
54+
} catch (RuntimeException e) {
55+
return e;
56+
}
57+
}
58+
59+
@Benchmark
60+
public String completedPromise() {
61+
return ofCompletedPromise.await();
62+
}
63+
64+
@Benchmark
65+
public Exception failedPromise() {
66+
try {
67+
ofFailedPromise.await();
68+
throw new AssertionError("Should have thrown exception");
69+
} catch (RuntimeException e) {
70+
return e;
71+
}
72+
}
73+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (c) 2011-2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
12+
package io.vertx.benchmarks;
13+
14+
import org.openjdk.jmh.annotations.Fork;
15+
16+
@Fork(value = 1, jvmArgs = {
17+
"-Djmh.executor=VIRTUAL"
18+
})
19+
public class FutureAwaitVirtualBenchmark extends FutureAwaitBenchmark {
20+
}

0 commit comments

Comments
 (0)