CompletableFuture as part of APIs and defensive copying

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

CompletableFuture as part of APIs and defensive copying

Vadim Shalts
Hello,

I am thinking about creating API with CompletableFuture usage.
CompletableFuture is great candidate to be used in interfaces of async
APIs for long running methods.
Also we often want to cache result (return the same heavy result a lot
of times).
Lets imagine some simple API for that (only CompletableFuture usage is
important here):

class SomeAPI {
     private CompletionStage<Integer> longRunningTask = null;
     private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);

     public synchronized CompletionStage<Integer> longRunningEvenNumber() {
         if (longRunningTask == null) {
             final CompletableFuture<Integer> promise = new
CompletableFuture();
             scheduler.schedule(() -> promise.complete(100), 1L,
TimeUnit.SECONDS);
             longRunningTask = promise;
         }
         return longRunningTask;//.thenApply(x -> x);
     }

     public void breakApiContract() {
         CompletionStage<Integer> f = longRunningEvenNumber();
         f.toCompletableFuture().complete(101);
     }
}

I see three variants how this can be done:
1) return "promise" directly. Not good for public API due we can easily
break contract by  toCompletableFuture().complete
2) Use defensive copying with thenApply(x -> x). Error-prone due easy to
forget. And will cause memory allocation on every call even if we will
not subscribe for results.
3) Implement custom read-only CompletionStage that will wrap "promise"
and cache this wrapper instead of original "promise". Still error-prone,
but I think it easier to understand and will no cost memory allocation
per each call. But maybe some other hidden costs which I missed.

Because this task look like useful in many cases for me I think that
third variant can be supported out of the box:

public synchronized CompletionStage<Integer> longRunningEvenNumber() {
         if (longRunningTask == null) {
             final CompletableFuture<Integer> promise = new
CompletableFuture();
             scheduler.schedule(() -> promise.complete(100), 1L,
TimeUnit.SECONDS);
             longRunningTask = promise.toReadOnlyStage();
         }
         return longRunningTask;
     }

Here imaginary method toReadOnlyStage() will return read-only
CompletionStage (which will return new instances for each call of
toCompletableFuture).

What do think?

Anyway, I believe that issue with toCompletableFuture().complete is not
obvious (was not obvious for me) and people may not see that they need
to use defensive copying in many cases when creating public APIs. Java 9
will receive copy() method, but this is not enough to spot issue. Maybe
some samples or notes should became part of documentation header of
CompletableFuture?

Best regards,
   Vadim Shalts.
_______________________________________________
Concurrency-interest mailing list
[hidden email]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest