Home > Software design >  Can an exception inside a whenComplete() affect the thread it spun off from?
Can an exception inside a whenComplete() affect the thread it spun off from?

Time:07-27

I was under the impression that anything you put in whenComplete, happens on a thread totally separate from the thread that spawned it. But we had an incident today where, looking at our logs, it seems as though an exception thrown from within the whenComplete bubbled up as if the exception were thrown in a regular chain (e.g. thenApply, thenCompose, etc.), and got caught by an exceptionally (in the original chain).

Is that possible? How?

I'm hoping the answer is No, that's not possible, and there's something weird going on / we're misreading our logs or logic.

CodePudding user response:

The Javadoc for CompletableFuture says, "...this method is not designed to translate completion outcomes, so the supplied action should not throw an exception." But then it tells you what to expect if the action does throw an exception...

it seems as though an exception thrown from within the whenComplete bubbled up as if the exception were thrown in a regular chain.

...The Javadoc says, "if this stage completed normally but the supplied action throws an exception, then the returned stage completes exceptionally with the supplied action's exception."

That sounds like the behavior you are seeing.


Response to comment:

But if task B throwing an exception causes task A (upon completing normally) to throw task B's exception, then doesn't that mean task A has to wait on task B?

No. There's no waiting. A CompletableFuture can be completed in either of two ways: It can be completed normally (e.g., as if by complete(T value)), or it can be completed exceptionally (as if by completeExceptionally(Throwable ex)). In the "exceptionally" case, a reference to the Throwable ex object will be stored in the future object.

Some time later, when some other thread eventually calls future.get() it will return the stored value if the the future was normally completed, but if the future was exceptionally completed, then the get() call will throw new ExecutionException(ex) where ex was the original Throwable object that was stored in the future.

So really, there's two exceptions thrown; One to report whatever the original bad thing was that happened when it happened, and then another that is deferred until some other thread calls future.get(). In your example, the "bad thing" happened when some pool thread performed the action that you supplied to whenComplete(...,action).

  • Related