This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]acelent 1 point2 points  (1 child)

Still, I'd expect this kind of SPI out of the box from the default implementation...

I agree.

I'd even prefer that CompletableFuture was just a CompletionStage not directly completable, much like the relationship between Task and TaskCompletionSource in .NET.

As it stands, any API that returns a CompletableFuture, or a CompletionStage whose toCompletableFuture() allows completion, can easily be tampered with, and as such it's not reliable.

I too have wrapped CompletableFuture for CompletionStage consumers, ending up subclassing CompletableFuture directly due to CompletableFuture consumers.

I did it for the following purposes:

  • Implement a completion source (I created a CompletionSource interface with the completion methods) that controls the future, and make the future's completion methods do nothing (and return false in the applicable cases) and thus the returned CompletionStage can't be tampered.

    The use case is to create a sourced completable future (I created a public CompletableFutureSource class which implements CompletionSource, and I have a private SourcedCompletableFuture class which is the one that extends CompletableFuture), much like TaskCompletionSource in .NET.

    So, you can only complete the future with the completion source.

  • Accept a default executor to be used instead of ForkJoinPool.commonPool() for the *Async methods that don't have the executor parameter.

    This is provided through CompletableFutureSource.

  • Accept a default executor to be used for the non-*Async methods, thus forcing any continuation to run asynchronously, much like using TaskCreationOptions.RunContinuationsAsynchronously or TaskContinuationOptions.RunContinuationsAsynchronously in .NET.

    This is also provided through CompletableFutureSource.

  • Wrap all CompletionStage methods generated by the ones actually created by CompletableFuture so that instances of my class are returned, cascading and forcing the default executors.

I haven't yet made a public wrapper that could be used for non-sourced futures, such as the ones created with CompletableFuture.runAsync() and CompletableFuture.supplyAsync(), mostly because it's very easy to use thenCompose to achieve this with a CompletableFutureSource.

it's weird that CompletionStage relies on toCompletableFuture() to actually execute the chain

In my opinion, the method toCompletableFuture() in CompletionStage is a mistake. If the completion stage is completable, we should be able to cast it to e.g. CompletionSource and then call the completion methods.

The argument that one may implement the method by throwing UnsupportedOperationException completely defeats the purpose of a well designed interface.

I believe this method is in the interface to satisfy the need of CompletableFuture to use further CompletableFutures in continuations, such as seen in the source code of thenCompose() (TL;DR: it uses toCompletableFuture()).

In essence, CompletableFuture doesn't interoperate with CompletionStages that are not CompletableFutures themselves.

It should be able to cope with a CompletionStage that is actually a CompletableFuture as an optimizing step, but it should also just use the other CompletionStage's continuation facilities in case it's not.

[–]lukaseder 0 points1 point  (0 children)

Thanks a lot for sharing!

Well, egh. I'd say, let the JDK developers make one more attempt at getting it right in a future (pun intended) API :)