Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it reasonable to throw an exception from an asynchronous method?

Developing in Java an asynchronous method with a CompletableFuture return type we expect the resulting CF to complete normally or exceptionally depending on whether that method succeeds or fails.

Yet, consider for instance that my method writes to an AsynchronousChannel and got an exception opening that channel. It has not even started writing. So, in this case I am tenting to just let the exception flow to the caller. Is that correct?

Although the caller will have to deal with 2 failure scenarios: 1) exception, or 2) rejected promise.

Or alternatively, should my method catch that exception and return a rejected promise instead?

like image 491
Miguel Gamboa Avatar asked Jan 16 '19 16:01

Miguel Gamboa


People also ask

What happens if an exception is thrown with an asynchronous method?

As we know, in asynchronous programming, control does not wait for the function's result and it executes the next line. So when the function throws an exception, at that moment the program control is out of the try-catch block.

What is asynchronous exception in Java?

Most exceptions occur synchronously as a result of an action by the thread in which they occur, and at a point in the program that is specified to possibly result in such an exception. An asynchronous exception is, by contrast, an exception that can potentially occur at any point in the execution of a program.

Why would you want to throw an exception?

Exceptions should be thrown when the contract between a method and its caller cannot be fulfilled.


2 Answers

IMO, option 1) makes the API harder to use because there will be two different paths for communicating errors:

  1. "Synchronous" exceptions, where the method ends the an exception being thrown.
  2. "Asynchronous" exceptions, where the method returns a CF, which completes with an exception. Note that it is impossible to avoid this case, because there will always be situations where the errors are only found after the asynchronous path has started (e.g. timeouts).

The programmer now has to ensure both these two paths are correctly handled, instead of just one.

It is also interesting to observe that the behaviour for both C# and Javascript is to always report exceptions thrown inside the body of an async function via the returned Task/Promise, even for exceptions thrown before the first await, and never by ending the async function call with an exception.

The same is also true for Kotlin's coroutines, even when using the Unconfined dispatcher

class SynchronousExceptionExamples {
    @Test
    fun example() {
        log.info("before launch")
        val job = GlobalScope.launch(Dispatchers.Unconfined) {
            log.info("before throw")
            throw Exception("an-error")
        }
        log.info("after launch")
        Thread.sleep(1000)
        assertTrue(job.isCancelled)
    }
}

will produce

6 [main] INFO SynchronousExceptionExamples - before launch
73 [main @coroutine#1] INFO SynchronousExceptionExamples - before throw
(...)
90 [main] INFO SynchronousExceptionExamples - after launch

Note as the exception occurs in the main thread, however launch ends with a proper Job.

like image 137
Pedro Felix Avatar answered Nov 15 '22 00:11

Pedro Felix


I think both are valid designs. Datastax actually started their design with first approach, where borrowing a connection was blocking, and switched to fully async model (https://docs.datastax.com/en/developer/java-driver/3.5/upgrade_guide/#3-0-4)

As a user of datastax java driver I was very happy with the fix, as it changed the api to be truly non-blocking (even opening a channel, in your example, has a cost).

But I don't think there are right and wrong here...

like image 23
Lior Chaga Avatar answered Nov 14 '22 23:11

Lior Chaga