Correctly handle exceptions in Akka async continuations - akka.net

To integrate async / task methods with akkling actor system I've written following method
let actorOfTask (t: 'a -> Task<'b>) =
(fun (ctx: Actor<_>) ->
let rec loop() =
actor {
let! data = ctx.Receive()
task {
try
let! t' = data |> t
return t'
with e ->
// TODO : Raise exception in order to supervisor can catch it
printf "Exception %O" e
return raise e
}
|> Async.AwaitTask
|!> ctx.Sender()
}
loop())
It is based on suggestion how to integrate actors with async workflows (example here https://github.com/Horusiath/Akkling/blob/0b5b0ffa4cd516407706ed230f81915452cdb183/tests/Akkling.Tests/Actors.fs#L124)
But issue here if async method throw exception it is not handled by parent supervisor
I'm expecting this code should work but it is not
let ss =
Strategy.OneForOne(fun error ->
printf "Error %O" error
Directive.Escalate)
let system = System.create "sys" <| Configuration.defaultConfig()
spawnAnonymous system { props (eventsHandler env) with SupervisionStrategy = Some ss }
Though if exception happens in regular actor flow (not integrated with async) this supervisor handle it.
Any suggestions how to fix it ?
Thanks

From Akka.Net documentation
https://getakka.net/articles/actors/receive-actor-api.html
WARNING
To complete the Task with an exception you need send a Failure message to the sender. This is not done automatically when an actor throws an exception while processing a message.
try
{
var result = operation();
Sender.Tell(result, Self);
}
catch (Exception e)
{
Sender.Tell(new Failure { Exception = e }, Self);
}
So actor should be implemented like this
let actorOfTask (sendTo: IActorRef<_>) (t: 'a -> Task<_>) =
(fun (ctx: Actor<_>) ->
let rec loop() =
actor {
let! data = ctx.Receive()
task {
try
let! t' = data |> t
return t' :> obj
with e ->
return Akka.Actor.Failure(Exception = e) :> obj
}
|> Async.AwaitTask
|!> (retype sendTo)
}
loop())

Related

Exception shows up in console although try...catch must prevent it

In my minimal API I try to save entity to database. The table contains UNIQUE constraint on license_plate column, so DbUpdateException would be thrown if same license plate would be passed in. I used try..catch in order to handle this situation:
app.MapPost("/vehicles", (VehiclesContext db, Vehicle vehicle) =>
{
var entity = db.Add(vehicle);
try
{
db.SaveChanges();
return Results.CreatedAtRoute("GetVehicle", new { inventoryNumber = entity.Entity.InventoryNumber }, entity.Entity);
}
catch (DbUpdateException)
{
var error = new JsonObject
{
["error"] = $"Creating vehicle failed because such license plate already exists: {vehicle.LicensePlate}"
};
return Results.BadRequest(error);
}
}).AddFilter<ValidationFilter<Vehicle>>();
However, when I pass duplicate license plate, I see this exception in console:
So, why does this exception show up in console? I tried to play with LogLevel for Microsoft.AspNetCore in appsettings.json (and appsettings.Development.json also) by changing Warning to Information, but still exceptions shows up.
The exception is logged prior to throwing, so you cannot stop the logging mechanism from being invoked.
However, you should be able to control output using LogLevel.
Note that the log comes from "Microsoft.EntityFrameworkCore" not "Microsoft.AspNetCore".
I just don't want to see errors which I handle in try...catch block!
Do you mean you don't want to see the fail ? Use Try-catch in minimal API?
Below is a demo, you can refer to it.
without try-catch
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>{
string s = null;
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
}}
);
app.Run();
result:
use try-catch:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () =>{
try
{
string s = null;
if (s == null)
{
throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
}
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
}
}
);
app.Run();
result:

Cannot handle exception in firebase function

i'm trying to understand with no luck why this throwable is not catched in my catch block:
CoroutineScope(IO).launch {
try { FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (task.isSuccessful) {
token = task.result
}
throw Exception("Hi There!")
}).await()
getUsers().await()
}catch (e: Exception){
binding.txtTitle.text = "Error: ${e.message}"
}
}
The exception is called but the app crash and not handle by the catch block. But if i throw an exception outside the addOnCompleteListener the exception is handled normally. My objective is to stop the execution of the getUsers function if no token is available.
The exception which is thrown in OnCompleteListener will not propagate to the outer scope, it is scoped to OnCompleteListener block. To achieve your objective I would recommend to rewrite the code to something like the following:
coroutineScope.launch {
try {
val token: String = FirebaseMessaging.getInstance().token.await()
if (token.isNotEmpty) {
getUsers().await()
}
} catch (e: Exception){
// ...
}
}
await function waits for task to complete.

Handling connection errors in Spring reactive webclient

I have a spring webclient making http calls to an external service and backed by reactive circuit breaker factory (resilience4J impl). WebClient and circuit breaker behave as expected when the client establishes connection and fails on response (Any internal server or 4XX errors). However, if the client fails to establish connection, either Connection Refused or UnknownHost, it starts to break down.
I cannot seem to catch the error message within the webclient and trigger circuit breaker.
Circuit breaker never opens and throws TimeoutException.
java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 1000ms in 'circuitBreaker' (and no fallback has been configured) .
Error from Web client.
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:9000 .
Here's my code. I have pasted the error origins as well. I have tried to map ConnectException to my custom exception for circuit breaker to pick up but, it did not work. Can someone help me on handling errors without the response from remote server?
public Mono<String> toSink(
Envelope envelope, ConsumerConfiguration webClientConfiguration) {
return getWebClient()
.post()
.uri(
uriBuilder -> {
if (webClientConfiguration.getPort() != null) {
uriBuilder.port(webClientConfiguration.getPort());
}
return uriBuilder.path(webClientConfiguration.getServiceURL()).build();
})
.headers(
httpHeaders ->
webClientConfiguration.getHttpHeaders().forEach((k, v) -> httpHeaders.add(k, v)))
.bodyValue(envelope.toString())
.retrieve()
.bodyToMono(Map.class)
// Convert 5XX internal server error and throw CB exception
.onErrorResume(
throwable -> {
log.error("Inside the error resume callback of webclient {}", throwable.toString());
if (throwable instanceof WebClientResponseException) {
WebClientResponseException r = (WebClientResponseException) throwable;
if (r.getStatusCode().is5xxServerError()) {
return Mono.error(new CircuitBreakerOpenException());
}
}
return Mono.error(new CircuitBreakerOpenException());
})
.map(
map -> {
log.info("Response map:{}", Any.wrap(map).toString());
return Status.SUCCESS.name();
})
.transform(
it -> {
ReactiveCircuitBreaker rcb =
reactiveCircuitBreakerFactory.create(
webClientConfiguration.getCircuitBreakerId());
return rcb.run(
it,
throwable -> {
/// "Did not observe any item or terminal signal within 1000ms.. " <--- Error here
log.info("throwable in CB {}", throwable.toString());
if (throwable instanceof CygnusBusinessException) {
return Mono.error(throwable);
}
return Mono.error(
new CircuitBreakerOpenException(
throwable, new CygnusContext(), null, null, null));
});
})
///io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:9000 <-- Error prints here
.onErrorContinue((throwable, o) -> log.error(throwable.toString()))
.doOnError(throwable -> log.error("error from webclient:{}", throwable.toString()));
}
I fixed it by adding an onErrorContinue block and re-throwing the exception as a custom that gets handled in my circuit breaker code.
.onErrorContinue(
(throwable, o) -> {
log.info("throwable => {}", throwable.toString());
if (throwable instanceof ReadTimeoutException || throwable instanceof ConnectException) {
throw new CircuitBreakerOpenException();
}
})
I would make the following suggestions on your solution:
1- There is an alternate variation of onErrorContinue which accepts a predicate so you can define which exceptions this operator will be applied to - Docs
2- Return a Mono.error instead of throwing RuntimeExceptions from Mono/Flux operators. This other stackoverflow answer covers this quite well - Stackoverflow
3- Perform logging with side effect operators (doOn*)
.doOnError(throwable -> log.info("throwable => {}", throwable.toString()))
.onErrorResume(throwable -> throwable instanceof ReadTimeoutException || throwable instanceof ConnectException,
t -> Mono.error(new CircuitBreakerOpenException()))
Hope this is helpful.

retryWhen used with flatMap throws exception

I wanted to retry in case of any exception from the service. But when using retryWhen am getting exception java.lang.IllegalStateException: UnicastProcessor allows only a single Subscriber.
Without retry, its working fine
Flux.window(10)
.flatMap(
windowedFlux ->
webclient.post().uri(url)
.body(BodyInserters.fromPublisher(windowedFlux, Request.class))
.exchange()
.doOnNext(ordRlsResponse -> {
if( ordRlsResponse.statusCode().is2xxSuccessful()) {
Mono<ResponseEntity<Response>> response = ordRlsResponse.toEntity(Response.class);
//doing some processing here
}
else {
throw new CustomeException(errmsg);
}
}).retryWhen(retryStrategy)).subscribe();
And my retryStrategy is defined like:
Retry retryStrategy = Retry.fixedDelay((long)5, Duration.ofSeconds((long)5))
.filter(exception -> exception instanceof CustomeException)
.doAfterRetry( exception -> log.info("Retry attempted"))

How do I force a method in Groovy to throw an exception

I wanted to write a test for a method in Groovy that throws an IOException. The only way for me to simulate this in the test is to force the method to throw this exception
This is what the original code looks like:
public void cleanUpBermudaFiles(RequestMessage requestMessage)
{
final File sourceDirectory = new File(preferenceService.getPreference("bermuda.landingstrip") + File.separator + requestMessage.getWorkflowId().getValue());
if(sourceDirectory!=null && sourceDirectory.exists())
{
deleteDirectory(sourceDirectory);
}
else
{
LOG.error("Directory must exist in order to delete");
}
}
private void deleteDirectory(File directoryToDelete)
{
try {
FileUtils.deleteDirectory(directoryToDelete);
} catch (Exception e) {
LOG.error("Failed to delete Bermuda files directory located at:" + directoryToDelete.getPath() + "with an exception" + e.getMessage());
}
}
MY TEST: (I'm looking for a way to make deleteDirectory throw IOException)
public void testCleanUpBermudaFailure()
{
workflowId = new WorkflowId("123456")
workflowDirectory = new File(srcDirectory, workflowId.value)
workflowDirectory.mkdir()
File.createTempFile('foo','.lst', workflowDirectory)
def exception = {throw new IOException()}
expect(mockRequestMessage.getWorkflowId()).andReturn(workflowId)
expect(mockPreferenceService.getPreference("bermuda.landingstrip")).andReturn(srcDirectory.path)
replay(mockPreferenceService, mockRequestMessage)
fileCleanUpService.preferenceService = mockPreferenceService
fileCleanUpService.metaClass.deleteDirectory = exception
fileCleanUpService.cleanUpBermudaFiles(mockRequestMessage)
verify(mockPreferenceService, mockRequestMessage)
assert srcDirectory.listFiles().length == 0, 'CleanUp failed'
}
If the service class is a Groovy class, you would want to mock FileUtils like:
FileUtils.metaClass.static.deleteDirectory = { File f -> throw new IOException() }
However, as ataylor pointed out, you cannot intercept calls if it's a Java class. You can find a nice blog post about it here.
You are mocking a no-arg call to deleteDirectory, but the real deleteDirectory takes one argument of type File. Try this:
def exception = { File directoryToDelete -> throw new IOException() }
...
fileCleanUpService.metaClass.deleteDirectory = exception