RFC: Future API
Migrate Vert.x to expose a future like API compatible.
The API will expose dual future/callback style.
class MyApi {
// The same method exposed with both styles
CompletionStage<T> doSomething();
void doSomething(Handler<AsyncResult<T>>);
}
Composing a resolved future should trampoline on the event loop instead of doing a direct execution, in order to provide a non-racy model that people can reason about.
Polyglot is affected since the new programming model needs to be translated into other languages.
-
Java API languages shall be fine out of the box
-
Ruby, JS should have their model upgraded to a promise or keep the original wrapping?
-
Code translation needs to be adapted as well
The io.vertx.core.Future
type is used as asynchronous type.
Any method returning Future<T>
will be erased from the codegen methods when there is a corresponding method with an Handler<AsyncResult<T>>
.
void method(String s, Handler<AsyncResult<Integer>> handler);
Future<Integer> method(Strings); // Is erased
This choice has been made since method carrying an Handler<AsyncResult<T>>
are clearly identified as asynchronous methods. Methods returning Future<T>
are currently ambiguous, e.g the Future<U> Future#map(Function<T, U>)
is not an asynchronous method.
-
asynchronous methods are declared as a callback method (like in 3.x)
-
any method returning a
Future
with the same signature than a callback method with its last parameter is erased and does not appear in the codegen class model -
other
Future
methods appear as regular methods
Re-use the existing Vert.x Future<T>
.
We introduced a new Promise<T>
type that handles the completion side and expose a Future<T>
.
-
3.8
-
deprecate write methods in
Future
-
introduce a
Promise
that exposes aFuture
-
overload the
Verticle
start
/stop
methods with thePromise
type -
replace the
executeBlocking(Handler<Future<T>> handler,…)
methods byexecuteBlocking(Handler<Promise<T>> handler,…)`
-
replace the
VerticleFactory#resolve(…, Future<String> fut)
byVerticleFactory#resolve(…, Promise<String> fut)
-
-
4.0
-
same as 3.8
-
remove
Future
write methods and it won’t extend anymoreHandler<AsyncResult<T>>
-
remove
Verticle
start
/stop
methods withFuture<T>
-
Until now Promise
are created out of thin air and the corresponding future does not provide a predictable callback
thread when used outside the event-loop model, when the future callback is set by another thread
-
before the promise resolution, the callback is executed on the promise resolver thread
-
after the promise resolution, the callback is executed on the current thread
In Vert.x 4 we add context promises, i.e promise capturing a Vert.x context that allows to always execute the future callback on the context thread.
Such promise are created from a ContextInternal
and shall be used (at least initially) by the API implementations:
Promise<String> promise = context.promise();
When the promise is resolved, the future handler will be executed according to the current thread and context association
-
on the same context
-
on event-loop context
-
on event-loop thread ⤇ execute handler on event-loop thread
-
otherwise ⤇ run handler on context (i.e resolving a promise in an execute blocking handler)
-
-
on worker thread
-
on event-loop thread ⤇ should it ever happen ?
-
on worker thread ⤇ execute handler on worker thread
-
-
-
on event-loop thread ⤇ execute handler on event-loop thread setting the context association (i.e resolving a promise from Netty callback)
-
otherwise ⤇ run handler on context
Handler failure are never be propagated to the caller, instead such failure is reported to the context promise.
Future propagates the context on asynchronous operations such as maop
, compose
, otherwise
, …
Issues to address:
-
Currently Vert.x future only supports a single handler, we want to support instead a list of listeners
-
API improvements
Future
operations that are pure events (they are fluent and won’t return a new future), e.g
-
onComplete(Handler<AsyncResult<T>>)
(currentlysetHandler
) -
onComplete(Handler<T>, Handler<Throwable>)
-
onSuccess(Handler<T>)
-
onFailure(Handler<Throwable>)
The existing setHandler
will be deprecated in 3.9.0 and removed in 4.0.
Extending the current compose
operation:
// Existing in 3.x
<U> Future<U> compose(Function<T, Future<U>> mapper);
// New
<U> Future<U> compose(Function<T, Future<U>> successMapper, Function<Throwable, Future<U>> failureMapper);
Add a flatMap(Function<T, Future<U>>)
delegating to compose(Function<T, Future<U>>)
.
// Existing in 3.x
<U> Future<U> compose(Function<T, Future<U>> mapper);
// New
default <U> Future<U> flatMap(Function<T, Future<U>> mapper) {
return compose(mapper);
}
The flatMap
operation is the common name used for such operation.
Two operations helping out interactions with CompletionStage
:
-
Future#toCompletionStage()
-
Future#fromCompletionStage(CompletionStage stage)
A Promise<T>
can be completed as a success
or failure
.
Currently we do have
-
fail(Throwable)
will set the failure completed state -
complete(T)
will set the success completed state
This looks asymetric, we could have instead
-
fail(Throwable
set the failure completed state -
succeed(T)
set the success completed state -
complete(Object)
set either failure (when Object is instance of Throwable) or success completed state
See this issue https://github.com/vert-x3/issues/issues/472
JavaScript and Ruby will return a Vert.x Future when no callback is provided, e.g
future = $vertx.create_net_client.connect(port, address)
future.set_handler { |err,res| ... }
This will allow code translation to operate on futures.
As the Vert.x Future
type is used, the methods are code generated and thus are regular Vert.x types which is the ideal case for code generation.
Here is an example of code translation from Java:
Future<String> fut = methodThatReturnsAFutureOfString();
fut.onSuccess(s -> {
System.out.println("Got the string " + s);
}).onFailure(err -> {
err.printStackTrace();
});
fut
.flatMap(res -> Future.failedFuture("fail it anyway"))
.onComplete(ar -> {
if (ar.succeeded()) {
System.out.println("Got the string " + ar.result());
} else {
ar.cause().printStackTrace();
}
});
To JavaScript:
var Future = require("vertx-js/future");
var fut = methodThatReturnsAFutureOfString();
fut.onSuccess(function (s) {
console.log("Got the string " + s);
}).onFailure(function (err) {
err.printStackTrace();
});
fut.flatMap(function (res) {
Future.failedFuture("fail it anyway");
}).onComplete(function (ar, ar_err) {
if (ar_err == null) {
console.log("Got the string " + ar);
} else {
ar_err.printStackTrace();
}
});