In code below I am fetching some data. If error/exception was thrown I want the exception handler to catch it. Once done with fetching, I am posting the result using LiveData to whoever is observing.
What I am trying to achieve is that the exception handler to finish its job once I post the result. Which means, if the observer handling the result also throws an exception, I don't want the coroutine exception handler to catch it (Which is the case in code below).
fun loadPrerequisites(resultObserver: MutableLiveData<PrerequisiteDataHolder?>) {
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
resultObserver.postValue(null)
}
scope.launch(Dispatchers.IO + exceptionHandler) {
val deferredCreationScheme = async {
fetchCreationScheme()
}
val creationScheme = deferredCreationScheme.await()
//TODO remove exception handler at this stage?
resultObserver.postValue(PrerequisiteDataHolder(creationScheme))
}
}
Is there a way to remove the exception handler before posting the result to the LiveData? Or must I introduce a new scope?
You seem to have misunderstood the purpose of the coroutine exception handler. It is the coroutine equivalent of uncaughtExceptionExceptionHandler in Java and its purpose is to inform you of an exception that has already broken its coroutine. You seem to want to use it to implement business logic-level exception handling.
The coroutine exception handler is not a replacement for the try-catch block, and the latter is what you should use in your case.
I think you don't need async in your code in the first place, I believe this is all you really need:
scope.launch(Dispatchers.IO) {
resultObserver.postValue(
try {
PrerequisiteDataHolder(fetchCreationScheme())
} catch (e: Exception) {
null
}
)
}
I typically use a helper function for code like this:
inline fun <T> tryOrNull(block: () -> T) = try {
block()
} catch (t: Throwable) {
null
}
Then your code becomes
scope.launch(Dispatchers.IO) {
tryOrNull { PrerequisiteDataHolder(fetchCreationScheme()) }
.also { resultObserver.postValue(it) }
}
Related
I have a Spring boot service with some code like below for parallel async call:
CompletableFuture future1 = accountManager.getResult(url1);
CompletableFuture future2 = accountManager.getResult(url2);
CompletableFuture.allOf(future1, future2).join();
String result1 = future1.get();
String result2 = future2.get();
It works fine when there is no exception. My question is how to handle exception? If getting future1 failed (let say url2 is an invalid url), I still want future2 back as partial result of allOf method. How should I do it?
Thanks!
CompletableFuture comes with a block called exceptionally() which can be used handle the exceptions happen inside the asynchronous code block. Snippet of getResult method for your reference,
public CompletableFuture<String> getGreeting(String url) {
return CompletableFuture.supplyAsync( () -> {
return // Business logic..
}, executor).exceptionally( ex -> {
log.error("Something went wrong : ", ex);
return null;
});
}
In this case the block would return null in case of exception and allOf method would lead to a completion where you can filter the one resulted in the exception when you fetch individual futures.
In my project I have this:
ParallelFlux<Device> flux = Flux.fromIterable(children)
.delayElements(Duration.ofMillis(10))
.parallel(18)
.runOn(Schedulers.elastic(), 10)
.doOnNext(c -> recursiveValidationThroughoutChildren(c, tracker)
});
Where recursiveValidationThroughoutChildren is a method with this declaration:
boolean recursiveValidationThroughoutChildren(Device d, NodeChangesTracker tracker) throws Exception;
What I don't understand is how to handle the exception thrown by this last method. I would like the exception to be propagated outside the ParallelFlux.
Is it possible? What is the correct way to handle it?
I followed the link #Rozart suggested, but I could not apply the solution as it is explained. I had to change it a bit:
ParallelFlux<Device> flux = Flux.fromIterable(children)
.delayElements(Duration.ofMillis(10))
.parallel(18)
.runOn(Schedulers.elastic(), 10)
.doOnNext(child -> {
try {
recursiveValidationThroughoutChildren(child, tracker);
} catch (Exception ex) {
Flux.error(ex);
}
});
The change is needed because the ParallelFlux does not support the "handle" method, so I had to add a try catch and relaunch the exception with a Flux.error.
I don't know if it is good practice, but it is the only way I got it work.
I have below code retuning Mono<Foo>:
try {
return userRepository.findById(id) // step 1
.flatMap(user -> barRepository.findByUserId( user.getId()) // step 2
.map(bar-> Foo.builder().msg("Already exists").build()) // step 3
.switchIfEmpty(barRepository.save(Bar.builder().userId(user.getId()).build()) // step 4
.map(bar-> Foo.builder().msg("Created").build()) // step 5
))
.doOnError(throwable -> Mono.just(handleError(throwable)));
} catch(Exception e) {
log.error("from catch block");
return Mono.just(handleError(e));
}
If error occurs in step 1 (e.g. user does not exist by the specified id), will it be caught by doOnError or by try catch block or none of these two?
Same question if error happens in step 2, step3, step 4.
What is the correct code so that error is always caught by doOnError and eliminate try catch?
I am using
public interface UserRepository extends ReactiveMongoRepository<User, String> same for barRepository.
handleError(throwable) simply does log.error(e.getMessage() and retuns Foo.
I think the first error is in the title: "Mono or Flux" is not related with the error handling.
Mono can only emit one item at the most (streams one element)
Flux can emit more complex stuff (i.e. List)
To handle errors you can follow this example:
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(ModelYouAreRetrieving.class)
.doOnError(throwable -> logger.error("Failed for some reason", throwable))
.onErrorReturn(new ModelYouAreRetrieving(...))
.block();
DoOnError will only perform side effects and assuming the findById are will return a Mono.Error() if it fails something like this should work.
return userRepository.findById(id)
.flatMap ( user ->
barRepository.findByUserId(user.getId())
.map((user,bar)-> Foo.builder().msg("Already exists").build())
.switchIfEmpty(barRepository.save(Bar.builder().userId(user.getId()).build())
.map(bar-> Foo.builder().msg("Created").build())
))
.onErrorReturn(throwable -> Mono.just(handleError(throwable)));
The try catch will only work if you either call a blocking operation of the chain, or a runtime error occurs before you enter the reactive chain. the doOn operations do not modify the chain, they are used for side effects only. Since flatMap expects a producer, you will need to return a Mono from the call, and in this case if an error occurs, then it will just propagate the error. In all reactive chains the error will propagate unless otherwise handled.
Use Exceptions.propagate(e) which wraps a checked exception into a special runtime exception that can be handled by onError
Below Code tries to covers User attributes in upper case. Now, when it encounters kyle the checked exception is throws and MIKE is returned from onErrorReturn
#Test
void Test19() {
Flux.fromIterable(Arrays.asList(new User("jhon", "10000"),
new User("kyle", "bot")))
.map(x -> {
try {
return toUpper(x);
} catch (TestException e) {
throw Exceptions.propagate(e);
}
})
.onErrorReturn(new User("MIKE", "BOT")).subscribe(x -> System.out.println(x));
}
protected final class TestException extends Exception {
private static final long serialVersionUID = -831485594512095557L;
}
private User toUpper(User user) throws TestException{
if (user.getName().equals("kyle")) {
throw new TestException();
}
return new User(user.getName().toUpperCase(), user.getProfession().toUpperCase());
}
Output
User [name=JHON, profession=10000]
User [name=MIKE, profession=BOT]
#Gianluca Pinto's last line of code is also incorrect. The code won't be compiled. onErrorReturn is not suitable for complicated error handling. What you should use is onErrorResume.
see: https://grokonez.com/reactive-programming/reactor/reactor-handle-error#21_By_falling_back_to_another_Flux
onErrorResume will fall back to another Flux and let you catch and manage the exception thrown by previous Flux. if look into the implementation of onErrorReturn, you will find onErrorReturn is actually using onErrorResume.
So here the code should be:
.onErrorResume(throwable -> Mono.just(handleError(throwable)));
The last line of the code of #James Ralston is wrong. The correct code should be:
return userRepository.findById(id)
.flatMap ( user ->
barRepository.findByUserId(user.getId())
.map((user,bar)-> Foo.builder().msg("Already exists").build())
.switchIfEmpty(barRepository.save(Bar.builder().userId(user.getId()).build())
.map(bar-> Foo.builder().msg("Created").build())
))
.onErrorReturn(Mono.just(handleError(throwable)));
While creating the reactive flow, we need to use onError* as it provides a fallback Mono/Flux while doOn* are side-effect operators.
NOTE: The examples are in Kotlin
Below is an example:
fun saveItems(item: Item) = testRepository.save(item)
.onErrorResume {
Mono.error(
onErrorResumeHandler(
it,
"APP-1002",
"Error occurred while saving the something :P, contact admin"
)
)
}
fun onErrorResumeHandler(exception: Throwable, errorCode: String, errorMessage: String) =
if (exception is TestRepositoryException) exception else
TestServiceException(errorCode, errorMessage)
There should be a central exception handler, we can create by extending AbstractErrorWebExceptionHandler. The order is -2 to supersede the default.
Below is an example:
#Component
#Order(-2)
class BaseControllerAdvice(
errorAttributes: ErrorAttributes,
resources: WebProperties.Resources,
applicationContext: ApplicationContext,
serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
val log = logger()
init {
setMessageWriters(serverCodecConfigurer.writers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes?) =
router {
RequestPredicates.all().invoke(this#BaseControllerAdvice::renderErrorResponse)
}
//RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse)
fun renderErrorResponse(
request: ServerRequest
): Mono<ServerResponse> {
val errorPropertiesMap = getErrorAttributes(
request,
ErrorAttributeOptions.defaults()
)
val ex: ApplicationException = getError(request) as ApplicationException
log.info("Error attributes:{}", request)
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(ErrorResponseVO(ex.errorCode, ex.errorMessage)))
}
data class ErrorResponseVO(val errorMessage: String, val errorCode: String)
}
I use Kovenant in my Kotlin application, and I'm calling Elasticsearch which has its own async API. I would rather use promises but the best I can come up with is something like:
task {
esClient.prepareSearch("index123")
.setQuery(QueryBuilders.matchAllQuery())
.execute().actionGet()
} then {
...
} success {
...
} fail {
...
}
Which makes an Kovenant async task thread, then Elasticsearch uses a thread from its pool, and then actionGet() synchronously blocks Elasticsearch to get back a result. It seems silly to spawn new threads while blocking others. Is there an approach to integrate the thread dispatching more closely?
Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that solutions for interesting problems are shared in SO.
You can use the Kovenant Deferred class to create a promise without dispatching via an async task as you did in your sample. The model is basically:
create a deferred instance
hook up to the async handlers and resolve or reject the deferred based on async callbacks
return the deferred.promise to the caller
In code, this would look like:
fun doSearch(): Promise<SearchResponse, Throwable> {
val deferred = deferred<Response, Throwable>()
esClient.prepareSearch("index")
.setQuery(QueryBuilders.matchAllQuery())
.execute(object: ActionListener<T> {
override fun onResponse(response: T) {
deferred.resolve(response)
}
override fun onFailure(e: Throwable) {
deferred.reject(e)
})
return deferred.promise
}
A re-usable way to do this is to first create an adapter that can just adapt Elasticsearch's desire for an ActionListener to work generically work with a promise:
fun <T: Any> promiseResult(deferred: Deferred<T, Exception>): ActionListener<T> {
return object: ActionListener<T> {
override fun onResponse(response: T) {
deferred.resolve(response)
}
override fun onFailure(e: Throwable) {
deferred.reject(wrapThrowable(e))
}
}
}
class WrappedThrowableException(cause: Throwable): Exception(cause.message, cause)
fun wrapThrowable(rawEx: Throwable): Exception = if (rawEx is Exception) rawEx else WrappedThrowableException(rawEx)
Note: the wrapThrowable() method is there to change a Throwable into an Exception because current versions (3.3.0) of Kovenant have some methods that expect the rejection type of the promise to descend from Exception (for example bind()) and you can stay with Throwable if you use unwrap() instead for nested promises.
Now use this adapter function to generically extend Elasticsearch ActionRequestBuilder which is pretty much the only thing you ever will call execute() on; creating a new promise() extension function:
fun <Request: ActionRequest<*>, Response: ActionResponse, RequestBuilder: ActionRequestBuilder<*, *, *, *>, Client: ElasticsearchClient<*>>
ActionRequestBuilder<Request, Response, RequestBuilder, Client>.promise(): Promise<Response, Exception> {
val deferred = deferred<Response, Exception>()
this.execute(promiseResult(deferred))
return deferred.promise
}
Now you can call promise() instead of execute():
esClient.prepareSearch("index")
.setQuery(QueryBuilders.matchAllQuery())
.promise()
And start chaining your promises...
esClient.admin().indices().prepareCreate("index1").setSettings("...").promise()
.bind {
esClient.admin().cluster().prepareHealth()
.setWaitForGreenStatus()
.promise()
} bind {
esClient.prepareIndex("index1", "type1")
.setSource(...)
.promise()
} bind {
esClient.prepareSearch("index1")
.setQuery(QueryBuilders.matchAllQuery())
.promise()
} then { searchResults ->
// ... use searchResults
}.success {
// ...
}.fail {
// ...
}
}
You should be familiar with bind() and unwrap() when you have nested promises you want to chain without nesting deeper. You can use unwrap().then in place of bind in the above cases if you did not want to include kovenant-functional.
Every call you have in Elasticsearch will be able to use promise() instead of execute() due to the consistent nature of all request objects in the Elasticsearch client.
I have a global ExceptionHandler in my web api 2.0, which handles all unhandled exceptions in order to return a friendly error message to the api caller.
I also have a global ExceptionFilter, which handles a very specific exception in my web api and returns a specific response. The ExceptionFilter is added dynamically by a plugin to my web api so I cannot do what it does in my ExceptionHandler.
I am wondering if I have both the ExceptionHandler and the ExceptionFilter registered globally, which one will take priority and be executed first? Right now I can see that the ExceptionFilter is being executed before the ExceptionHandler. And I can also see that in my ExceptionFilter if I create a response the ExceptionHandler is not being executed.
Will it be safe to assume that:
ExceptionFilters are executed before ExceptionHandlers.
If the ExceptionFilter creates a response, the ExceptionHandler will not be executed.
I had to debug through the System.Web.Http in order to find the answer to my question. So the answer is:
It is safe to assume that ExceptionFilters will be executed before ExceptionHandlers
If the ExceptionFilter creates a response the ExceptionHandler would not be executed.
Why this is so:
When you have an ExceptionFilter registered to execute globally or for your controller action, the ApiController base class from which all the api Controllers inherit will wrap the result in an ExceptionFilterResult and call its ExecuteAsync method. This is the code in the ApiController, which does this:
if (exceptionFilters.Length > 0)
{
IExceptionLogger exceptionLogger = ExceptionServices.GetLogger(controllerServices);
IExceptionHandler exceptionHandler = ExceptionServices.GetHandler(controllerServices);
result = new ExceptionFilterResult(ActionContext, exceptionFilters, exceptionLogger, exceptionHandler,
result);
}
return result.ExecuteAsync(cancellationToken);
Looking at the ExceptionFilterResult.ExecuteAsync method:
try
{
return await _innerResult.ExecuteAsync(cancellationToken);
}
catch (Exception e)
{
exceptionInfo = ExceptionDispatchInfo.Capture(e);
}
// This code path only runs if the task is faulted with an exception
Exception exception = exceptionInfo.SourceException;
Debug.Assert(exception != null);
bool isCancellationException = exception is OperationCanceledException;
ExceptionContext exceptionContext = new ExceptionContext(
exception,
ExceptionCatchBlocks.IExceptionFilter,
_context);
if (!isCancellationException)
{
// We don't log cancellation exceptions because it doesn't represent an error.
await _exceptionLogger.LogAsync(exceptionContext, cancellationToken);
}
HttpActionExecutedContext executedContext = new HttpActionExecutedContext(_context, exception);
// Note: exception filters need to be scheduled in the reverse order so that
// the more specific filter (e.g. Action) executes before the less specific ones (e.g. Global)
for (int i = _filters.Length - 1; i >= 0; i--)
{
IExceptionFilter exceptionFilter = _filters[i];
await exceptionFilter.ExecuteExceptionFilterAsync(executedContext, cancellationToken);
}
if (executedContext.Response == null && !isCancellationException)
{
// We don't log cancellation exceptions because it doesn't represent an error.
executedContext.Response = await _exceptionHandler.HandleAsync(exceptionContext, cancellationToken);
}
You can see that the ExceptionLogger is executed first, then all ExceptionFilters are executed and then if if executedContext.Response == null, the ExceptionHandler is executed.
I hope this is useful!