[concurrency-interest] Layered exception handling with CompletableFuture

Doug Lea dl at cs.oswego.edu
Sat Aug 30 10:51:30 EDT 2014

Peter: Thanks for posting the nice explanations of options for
processing exceptions in CompletableFuture.  Here are a few notes
about some of the underlying design issues.

* The main rationale for introducing CompletableFuture was to finally
provide an efficient common basis for fluent completion / continuation
/ flow-based async programming.  Adding this was arguably a decade or
so too late (this style of programming was the only one discussed in
my 1999 CPJ book (sec 4.2) without good java.util.concurrent support).
But we couldn't realistically do this in j.u.c before the introduction
of lambdas and functional interfaces in JDK8.  (Non-JDK async
frameworks that did so had to invent workarounds for the lack of
these.)  For good reasons, JDK8 lambdas/types do not nicely accommodate
checked exceptions.  CompletableFuture turns out to be the first place
some people notice this. However, as others have already explained, no
actual functionality is lost; instead you may need to awkwardly move
in and out of checked vs unchecked mode, usually by inspecting
CompletionExceptions.  This awkwardness typically outweighs the
awkwardness users would otherwise have to suffer to express simple
stage processing.

* Flow-based async programming frameworks tend to differ in policies
about whether and how to support cancellation, blocking, async vs sync
completions, rescue-overrides, etc., and how users should be able to
express them. CompletableFuture tries to avoid these issues by
providing efficient mechanics that are as policy-free as we can make
them (at the expense of an API larger than some people like), while at
the same time staying light enough that others can wrap, restrict, or
extend them to support different usage styles with little to no
penalty versus building them from scratch. For example, most people
have found that it is easy to use CompletableFuture as a form of
"Promise", either by wrapping or just via usage conventions.  There
are surely framework developers who would still rather build from
scratch, but fewer than before the introduction of CompletableFuture.

* In part because of uncertainty whether the audience for
CompletableFuture would mainly be users vs framework authors, we did
not in JDK8 take the next step of introducing standard ways of
producing streams of CompletableFutures or their contents
(values/elements/events), each of which is the head of a completion
expression. As in "periodically { supply(...).thenApply(...)....; }"
It is easy to define utilities automating common use cases, including
simple periodic suppliers (via a common ScheduledExecutor), and
async-io completions (via adaptors from nio CompletionHandler).
However, beyond this, the policy/protocol space becomes hard to cope
with for a java.util.concurrent component: Do you want/need
rate-limiting, overrun, and drop policies? Synchronous or asynchronous
acks/flow-control/back-pressure? Batching/Buffering?  Logging?
Conversion into (and out of) java.util.Streams for (possibly parallel)
bulk operations on batches?  StreamIt-like split/merge parallelization
of in-flight processing? And so on. It might still be premature to
settle on a single form in the spirit/style of CompletableFuture that
can serve as a common basis for all (or most) of these. In the mean
time, it seems wrong to not provide some common utilities for delays
and IO, so we might at least offer a "CompletableFutures" utility
class including them, as well as exception-mode-conversion utilities,
plus probably some means of simplifying interop with frameworks like
reactive-streams and Rx.

* Bear in mind that CompletableFutures (and related constructs) are
far from the only ways to compose async processing. They are a lot
easier to use than most others though, because they make it easy to
express many staged processing flows that arise in practice.  Other
harder-to-used constructs (like CountedCompleters, or hand-crafted
combinations of threads, queues, and synchronizers) remain available
and worth considering if you routinely find yourself frustrated trying
to express unusual control constructs within the confines of
CompletableFuture's fluent API.


More information about the Concurrency-interest mailing list