Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace custom Future implementations by CompletableFuture #32512

Closed
wants to merge 20 commits into from

Conversation

ywelsch
Copy link
Contributor

@ywelsch ywelsch commented Jul 31, 2018

This PR replaces our custom future implementations by Java 8's CompletableFuture, which results in less custom code to maintain (see PR stats), and enables the use of more powerful async programming features across our codebase. Furthermore, it fixes an issue with CompletableFuture, where it's not properly bubbling up Errors to the uncaught exception handler.

@ywelsch ywelsch added >enhancement :Core/Infra/Core Core issues without another label v7.0.0 labels Jul 31, 2018
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra

@jasontedor jasontedor self-requested a review July 31, 2018 18:27
Copy link
Contributor

@bleskes bleskes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome. I left minor comments.

}
whenComplete((val, throwable) -> {
if (throwable == null) {
listener.onResponse(val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old code caught exceptions here and routed them to listener.onFailure()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'm not a fan of having this anti-pattern in library code but I'm still making this change for BWC purposes. In the future (pun intended) we can also think about adding these methods directly to BaseFuture as every Future has now listening capabilities.

ClusterApplierService.assertNotClusterStateUpdateThread(BLOCKING_OP_REASON) &&
MasterService.assertNotMasterUpdateThread(BLOCKING_OP_REASON));
return sync.get(unit.toNanos(timeout));
assert timeout <= 0 || blockingAllowed();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

notifyListener(listener, EsExecutors.newDirectExecutorService());
whenCompleteAsync((val, throwable) -> {
if (throwable == null) {
listener.onResponse(val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment about exception handling.

@Tim-Brooks
Copy link
Contributor

I have a comment. This is similar to work I did in org.elasticsearch.common.concurrent.CompletableContext. One thing that I have noticed recently is that CompletableFuture catches Throwable that occurs in listeners. So:

CompletableContext<Object> context = new CompletableContext<>();
context.addListener((o, e) -> {
        assert false : "Should not fail";
});
context.complete(null);

That assertion is swallowed and never seen again. This is because when the listener is executed, CompletableFuture catches it and completes the other CompletableFuture that was created when the listener was attached. You have to manually interrogate that future to find it.


    final boolean uniWhenComplete(Object r,
                                  BiConsumer<? super T,? super Throwable> f,
                                  UniWhenComplete<T> c) {
        T t; Throwable x = null;
        if (result == null) {
            try {
                if (c != null && !c.claim())
                    return false;
                if (r instanceof AltResult) {
                    x = ((AltResult)r).ex;
                    t = null;
                } else {
                    @SuppressWarnings("unchecked") T tr = (T) r;
                    t = tr;
                }
                f.accept(t, x);
                if (x == null) {
                    internalComplete(r);
                    return true;
                }
            } catch (Throwable ex) {
                if (x == null)
                    x = ex;
                else if (x != ex)
                    x.addSuppressed(ex);
            }
            completeThrowable(x, r);
        }
        return true;
    }

I had not decided how I was going to fix that issue. But I was thinking of wrapping attached listeners, catching Error and doing the throw it to another thread thing.

I just thought I would mention that here as I assume it will impact this work and it was causing me issues for assertions to disappear.

@jasontedor
Copy link
Member

Note that we handled a similar problem in #28667.

@ywelsch ywelsch added the WIP label Dec 3, 2018
@Tim-Brooks
Copy link
Contributor

The recent updates seem like they will work. The only downside seems to be that all the CompletionStage Apis (like whenComplete) take Throwable. People that use the BaseFuture will still have to deal with Throwable. But I think that is unavoidable without essentially creating our own CompletionStage interface that does not take Throwable.

@ywelsch ywelsch removed the WIP label Dec 27, 2018
@ywelsch
Copy link
Contributor Author

ywelsch commented Dec 27, 2018

I have reworked this PR so that the CompletableFutures now properly bubble up Errors to the uncaught exception handler.

People that use the BaseFuture will still have to deal with Throwable.

yes, but only if they make use of the methods on CompletionStage that require to do so. We can also provide convenient alternatives on BaseFuture if need be.

As follow-ups, I would like to:

  • rename BaseFuture to something like just ESFuture
  • remove some of the subclasses of BaseFuture, as the functionality is now directly available in the super class (e.g. ListenableFuture).

@elasticsearchmachine
Copy link
Collaborator

Hi @ywelsch, I've created a changelog YAML for you.

@mark-vieira mark-vieira added v8.2.0 and removed v8.1.0 labels Feb 2, 2022
@elasticsearchmachine elasticsearchmachine changed the base branch from master to main July 22, 2022 23:15
@mark-vieira mark-vieira added v8.5.0 and removed v8.4.0 labels Jul 27, 2022
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra (Team:Core/Infra)

@csoulios csoulios added v8.6.0 and removed v8.5.0 labels Sep 21, 2022
@kingherc kingherc added v8.7.0 and removed v8.6.0 labels Nov 16, 2022
@javanna javanna closed this Nov 30, 2022
@javanna javanna removed the v8.7.0 label Nov 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Core/Infra/Core Core issues without another label >enhancement Team:Core/Infra Meta label for core/infra team
Projects
None yet
Development

Successfully merging this pull request may close these issues.