Hi Thomas and Julien,
When I encountered the problem, and after I figured out it originates from "... Future<T> executeBlocking( ...",
and knowing that executeBlocking uses worker thread pool to execute submitted lambda,
I knew immediately that multi-threaded concurrency is involved somewhere to cause the problem.
But couldn't find anything wrong in executeBlocking code.
So I filled in the bug report and created the reproducer because I regard this as a bug.
Now Thomas correctly pointed me to the code in FutureImpl that causes the problems WHEN "the future is completed on some other thread"
But concerns about something executing from another thread are from domain of multi-threaded concurrency.
And Vert.x provides concurrency with event loop and non blocking asynchronous API, not by executing the code in multiple threads.
Sharing and DIRECTLY using objects between threads is major "NO! NO!" for any loop event based framework.
They all advice to do so INDIRECTLY using some equivalent of "please run this on the event loop when you can" method, such as runOnContext().
I'm bit surprised Vert.x core team didn't see this problem as a bug but went to "the order is not guaranteed" direction.
So now that Thomas explained exactly where is the problem, I figured out exactly what is wrong with executeBlocking.
It is in its line: return fut;
It should have been: return fut.map(result -> result);
Why?
Because fut's promise is the object that is passed into blockingCodeHandler
and this object is used in blockingCodeHandler code to either complete() or fail() the promise
but this completion happens in separate worker pool thread away from event loop thread
and by returning this object via "return fut;" to the caller
we are leaking the object that is shared between the threads into caller code
causing the effects not expected for the code that deal with "event loop concurrency"
So simple return fut.map(result -> result);
solves the problem because map() creates new promise and return its future to the caller code.
I'd like you not to go to direction of further confusing users with "the order is not guaranteed" because "some other thread might be involved".
Actually, currently, the JavaDoc is amended to mention "the order is not guaranteed" but without explanation.
If you were to add explanation and explanation would be "because the future could be completed on some other thread" it would be even more confusing.
And all this looks like stepping outside of Vert.x domain to me.
You even added new Future method andThen() method to provide order guarantee in that commit where JavaDoc is changed with with "the order is not guaranteed".
But andThen() looks just like fancy map().
And that's why .map(result -> result) also works.
I'm not happy where Vert.x is going regarding this, but you are the project leads and it is up to you ultimately.
I will continue to "assume" the order of execution follows the order of registration.
I already applied the fix with map(). Just I created separate executeBlocking() utility method where this map(result -> result) is applied
and use this utility method everywhere I need executeBlocking().
public static <Result> Future<Result> executeBlocking(Vertx vertx, Handler<Promise<Result>> blockingCodeHandler) {
return vertx.<Result>executeBlocking(blockingCodeHandler).map(result -> result);